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_observer.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
   |          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 "ext/standard/php_array.h" /* For PHP_COUNT_* constants */
22
#include "ext/standard/php_var.h"
23
#include "zend_smart_str.h"
24
#include "zend_interfaces.h"
25
#include "zend_exceptions.h"
26
#include "zend_attributes.h"
27
28
#include "php_spl.h" /* For php_spl_object_hash() */
29
#include "spl_observer.h"
30
#include "spl_observer_arginfo.h"
31
#include "spl_iterators.h"
32
#include "spl_exceptions.h"
33
#include "spl_functions.h" /* For spl_set_private_debug_info_property() */
34
35
PHPAPI zend_class_entry     *spl_ce_SplObserver;
36
PHPAPI zend_class_entry     *spl_ce_SplSubject;
37
PHPAPI zend_class_entry     *spl_ce_SplObjectStorage;
38
PHPAPI zend_class_entry     *spl_ce_MultipleIterator;
39
40
static zend_object_handlers spl_handler_SplObjectStorage;
41
static zend_object_handlers spl_handler_MultipleIterator;
42
43
/* Bit flags for marking internal functionality overridden by SplObjectStorage subclasses. */
44
26
#define SOS_OVERRIDDEN_READ_DIMENSION  1
45
26
#define SOS_OVERRIDDEN_WRITE_DIMENSION 2
46
26
#define SOS_OVERRIDDEN_UNSET_DIMENSION 4
47
48
ZEND_TLS uint32_t spl_object_storage_get_hash_depth;
49
50
void spl_object_storage_reset_get_hash_depth(void)
51
300k
{
52
300k
  spl_object_storage_get_hash_depth = 0;
53
300k
}
54
55
typedef struct _spl_SplObjectStorage { /* {{{ */
56
  HashTable         storage;
57
  zend_long         index;
58
  HashPosition      pos;
59
  /* In SplObjectStorage, flags is a hidden implementation detail to optimize ArrayAccess handlers.
60
   * In MultipleIterator on a different class hierarchy, flags is a user settable value controlling iteration behavior. */
61
  zend_long         flags;
62
  zend_function    *fptr_get_hash;
63
  zend_object       std;
64
} spl_SplObjectStorage; /* }}} */
65
66
/* {{{ storage is an assoc array of [zend_object*]=>[zval *obj, zval *inf] */
67
typedef struct _spl_SplObjectStorageElement {
68
  zend_object *obj;
69
  zval inf;
70
} spl_SplObjectStorageElement; /* }}} */
71
72
119k
#define spl_object_storage_from_obj(obj) ZEND_CONTAINER_OF(obj, spl_SplObjectStorage, std)
73
74
24.1k
#define Z_SPLOBJSTORAGE_P(zv)  spl_object_storage_from_obj(Z_OBJ_P((zv)))
75
76
static zend_always_inline bool spl_object_storage_is_mutating_within_get_hash_call(void)
77
1.81k
{
78
1.81k
  if (UNEXPECTED(spl_object_storage_get_hash_depth)) {
79
0
    zend_throw_error(NULL, "Modification of SplObjectStorage during getHash() is prohibited");
80
0
    return true;
81
0
  }
82
83
1.81k
  return false;
84
1.81k
}
85
86
static void spl_SplObjectStorage_free_storage(zend_object *object) /* {{{ */
87
29.2k
{
88
29.2k
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
89
90
29.2k
  zend_object_std_dtor(&intern->std);
91
92
29.2k
  zend_hash_destroy(&intern->storage);
93
29.2k
} /* }}} */
94
95
81
static zend_result spl_object_storage_get_hash(zend_hash_key *key, spl_SplObjectStorage *intern, zend_object *obj) {
96
81
  if (UNEXPECTED(intern->fptr_get_hash)) {
97
0
    zval param;
98
0
    zval rv;
99
0
    ZVAL_OBJ(&param, obj);
100
0
    ZVAL_UNDEF(&rv);
101
0
    spl_object_storage_get_hash_depth++;
102
0
    zend_call_method_with_1_params(&intern->std, intern->std.ce, &intern->fptr_get_hash, "getHash", &rv, &param);
103
0
    spl_object_storage_get_hash_depth--;
104
0
    if (UNEXPECTED(Z_ISUNDEF(rv))) {
105
      /* An exception has occurred */
106
0
      return FAILURE;
107
0
    } else {
108
      /* TODO PHP 9: Remove this as this will be enforced from the return type */
109
0
      if (UNEXPECTED(Z_TYPE(rv) != IS_STRING)) {
110
0
        zend_type_error("%s::getHash(): Return value must be of type string, %s returned",
111
0
          ZSTR_VAL(intern->std.ce->name), zend_zval_value_name(&rv));
112
0
        zval_ptr_dtor(&rv);
113
0
        return FAILURE;
114
0
      } else {
115
0
        key->key = Z_STR(rv);
116
0
        return SUCCESS;
117
0
      }
118
0
    }
119
81
  } else {
120
81
    key->key = NULL;
121
81
    key->h = obj->handle;
122
81
    return SUCCESS;
123
81
  }
124
81
}
125
126
81
static void spl_object_storage_free_hash(spl_SplObjectStorage *intern, zend_hash_key *key) {
127
81
  if (key->key) {
128
0
    zend_string_release_ex(key->key, 0);
129
0
  }
130
81
}
131
132
static void spl_object_storage_dtor(zval *element) /* {{{ */
133
1.78k
{
134
1.78k
  spl_SplObjectStorageElement *el = Z_PTR_P(element);
135
1.78k
  if (el) {
136
1.78k
    zend_object_release(el->obj);
137
1.78k
    zval_ptr_dtor(&el->inf);
138
1.78k
    efree(el);
139
1.78k
  }
140
1.78k
} /* }}} */
141
142
static spl_SplObjectStorageElement* spl_object_storage_get(spl_SplObjectStorage *intern, zend_hash_key *key) /* {{{ */
143
81
{
144
81
  if (key->key) {
145
0
    return zend_hash_find_ptr(&intern->storage, key->key);
146
81
  } else {
147
81
    return zend_hash_index_find_ptr(&intern->storage, key->h);
148
81
  }
149
81
} /* }}} */
150
151
static spl_SplObjectStorageElement *spl_object_storage_create_element(zend_object *obj, zval *inf) /* {{{ */
152
1.78k
{
153
1.78k
  spl_SplObjectStorageElement *pelement = emalloc(sizeof(spl_SplObjectStorageElement));
154
1.78k
  pelement->obj = obj;
155
1.78k
  GC_ADDREF(obj);
156
1.78k
  if (inf) {
157
1.69k
    ZVAL_COPY(&pelement->inf, inf);
158
1.69k
  } else {
159
87
    ZVAL_NULL(&pelement->inf);
160
87
  }
161
1.78k
  return pelement;
162
1.78k
} /* }}} */
163
164
/* A faster version of spl_object_storage_attach used when neither SplObjectStorage->getHash nor SplObjectStorage->offsetSet is overridden. */
165
static spl_SplObjectStorageElement *spl_object_storage_attach_handle(spl_SplObjectStorage *intern, zend_object *obj, zval *inf) /* {{{ */
166
1.81k
{
167
1.81k
  uint32_t handle = obj->handle;
168
1.81k
  zval *entry_zv = zend_hash_index_lookup(&intern->storage, handle);
169
1.81k
  spl_SplObjectStorageElement *pelement;
170
1.81k
  ZEND_ASSERT(!(intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION));
171
172
1.81k
  if (Z_TYPE_P(entry_zv) != IS_NULL) {
173
35
    zval zv_inf;
174
35
    ZEND_ASSERT(Z_TYPE_P(entry_zv) == IS_PTR);
175
35
    pelement = Z_PTR_P(entry_zv);
176
35
    ZVAL_COPY_VALUE(&zv_inf, &pelement->inf);
177
35
    if (inf) {
178
8
      ZVAL_COPY(&pelement->inf, inf);
179
27
    } else {
180
27
      ZVAL_NULL(&pelement->inf);
181
27
    }
182
    /* Call the old value's destructor last, in case it moves the entry */
183
35
    zval_ptr_dtor(&zv_inf);
184
35
    return pelement;
185
35
  }
186
187
  /* NULL initialization necessary because `spl_object_storage_create_element` could bail out due to OOM. */
188
1.78k
  ZVAL_PTR(entry_zv, NULL);
189
1.78k
  pelement = spl_object_storage_create_element(obj, inf);
190
1.78k
  Z_PTR_P(entry_zv) = pelement;
191
1.78k
  return pelement;
192
1.81k
} /* }}} */
193
194
static spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *intern, zend_object *obj, zval *inf) /* {{{ */
195
1.72k
{
196
1.72k
  if (UNEXPECTED(spl_object_storage_is_mutating_within_get_hash_call())) {
197
0
    return NULL;
198
0
  }
199
200
1.72k
  if (EXPECTED(!(intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION))) {
201
1.72k
    return spl_object_storage_attach_handle(intern, obj, inf);
202
1.72k
  }
203
  /* getHash or offsetSet is overridden. */
204
205
0
  spl_SplObjectStorageElement *pelement, element;
206
0
  zend_hash_key key;
207
0
  if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) {
208
0
    return NULL;
209
0
  }
210
211
0
  pelement = spl_object_storage_get(intern, &key);
212
213
0
  if (pelement) {
214
0
    zval zv_inf;
215
0
    ZVAL_COPY_VALUE(&zv_inf, &pelement->inf);
216
0
    if (inf) {
217
0
      ZVAL_COPY(&pelement->inf, inf);
218
0
    } else {
219
0
      ZVAL_NULL(&pelement->inf);
220
0
    }
221
0
    spl_object_storage_free_hash(intern, &key);
222
    /* Call the old value's destructor last, in case it moves the entry */
223
0
    zval_ptr_dtor(&zv_inf);
224
0
    return pelement;
225
0
  }
226
227
0
  element.obj = obj;
228
0
  GC_ADDREF(obj);
229
0
  if (inf) {
230
0
    ZVAL_COPY(&element.inf, inf);
231
0
  } else {
232
0
    ZVAL_NULL(&element.inf);
233
0
  }
234
0
  if (key.key) {
235
0
    pelement = zend_hash_update_mem(&intern->storage, key.key, &element, sizeof(spl_SplObjectStorageElement));
236
0
  } else {
237
0
    pelement = zend_hash_index_update_mem(&intern->storage, key.h, &element, sizeof(spl_SplObjectStorageElement));
238
0
  }
239
0
  spl_object_storage_free_hash(intern, &key);
240
0
  return pelement;
241
0
} /* }}} */
242
243
static zend_result spl_object_storage_detach(spl_SplObjectStorage *intern, zend_object *obj) /* {{{ */
244
0
{
245
0
  if (UNEXPECTED(spl_object_storage_is_mutating_within_get_hash_call())) {
246
0
    return FAILURE;
247
0
  }
248
249
0
  if (EXPECTED(!(intern->flags & SOS_OVERRIDDEN_UNSET_DIMENSION))) {
250
0
    return zend_hash_index_del(&intern->storage, obj->handle);
251
0
  }
252
0
  zend_result ret = FAILURE;
253
0
  zend_hash_key key;
254
0
  if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) {
255
0
    return ret;
256
0
  }
257
0
  if (key.key) {
258
0
    ret = zend_hash_del(&intern->storage, key.key);
259
0
  } else {
260
0
    ret = zend_hash_index_del(&intern->storage, key.h);
261
0
  }
262
0
  spl_object_storage_free_hash(intern, &key);
263
264
0
  return ret;
265
0
} /* }}}*/
266
267
/* TODO: make this an official Zend API? */
268
0
#define SPL_SAFE_HASH_FOREACH_PTR(_ht, _ptr) do { \
269
0
    const HashTable *__ht = (_ht); \
270
0
    zval *_z = __ht->arPacked; \
271
0
    for (uint32_t _idx = 0; _idx < __ht->nNumUsed; _idx++, _z = ZEND_HASH_ELEMENT(__ht, _idx)) { \
272
0
      if (UNEXPECTED(Z_ISUNDEF_P(_z))) continue; \
273
0
      _ptr = Z_PTR_P(_z);
274
275
0
static zend_result spl_object_storage_addall(spl_SplObjectStorage *intern, spl_SplObjectStorage *other) { /* {{{ */
276
0
  spl_SplObjectStorageElement *element;
277
278
0
  SPL_SAFE_HASH_FOREACH_PTR(&other->storage, element) {
279
0
    zval zv;
280
0
    zend_object *obj = element->obj;
281
0
    GC_ADDREF(obj);
282
0
    ZVAL_COPY(&zv, &element->inf);
283
0
    spl_SplObjectStorageElement *attached = spl_object_storage_attach(intern, obj, &zv);
284
0
    zval_ptr_dtor(&zv);
285
0
    OBJ_RELEASE(obj);
286
0
    if (UNEXPECTED(!attached)) {
287
0
      return FAILURE;
288
0
    }
289
0
  } ZEND_HASH_FOREACH_END();
290
291
0
  intern->index = 0;
292
0
  return SUCCESS;
293
0
} /* }}} */
294
295
#define SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zstr_method) \
296
104
  (class_type->arrayaccess_funcs_ptr && class_type->arrayaccess_funcs_ptr->zstr_method)
297
298
static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend_object *orig) /* {{{ */
299
29.2k
{
300
29.2k
  spl_SplObjectStorage *intern;
301
29.2k
  const zend_class_entry *parent = class_type;
302
303
29.2k
  intern = zend_object_alloc(sizeof(spl_SplObjectStorage), parent);
304
29.2k
  intern->pos = 0;
305
306
29.2k
  zend_object_std_init(&intern->std, class_type);
307
29.2k
  object_properties_init(&intern->std, class_type);
308
309
29.2k
  zend_hash_init(&intern->storage, 0, NULL, spl_object_storage_dtor, 0);
310
311
29.5k
  while (parent) {
312
29.2k
    if (parent == spl_ce_SplObjectStorage) {
313
      /* Possible optimization: Cache these results with a map from class entry to IS_NULL/IS_PTR.
314
       * Or maybe just a single item with the result for the most recently loaded subclass. */
315
28.9k
      if (class_type != spl_ce_SplObjectStorage) {
316
26
        zend_function *get_hash = zend_hash_str_find_ptr(&class_type->function_table, "gethash", sizeof("gethash") - 1);
317
26
        if (get_hash->common.scope != spl_ce_SplObjectStorage) {
318
0
          intern->fptr_get_hash = get_hash;
319
0
        }
320
26
        if (intern->fptr_get_hash != NULL ||
321
26
          SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetget) ||
322
26
          SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetexists)) {
323
26
          intern->flags |= SOS_OVERRIDDEN_READ_DIMENSION;
324
26
        }
325
326
26
        if (intern->fptr_get_hash != NULL ||
327
26
          SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetset)) {
328
26
          intern->flags |= SOS_OVERRIDDEN_WRITE_DIMENSION;
329
26
        }
330
331
26
        if (intern->fptr_get_hash != NULL ||
332
26
          SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, zf_offsetunset)) {
333
26
          intern->flags |= SOS_OVERRIDDEN_UNSET_DIMENSION;
334
26
        }
335
26
      }
336
28.9k
      break;
337
28.9k
    }
338
339
295
    parent = parent->parent;
340
295
  }
341
342
29.2k
  if (orig) {
343
0
    spl_SplObjectStorage *other = spl_object_storage_from_obj(orig);
344
0
    spl_object_storage_addall(intern, other);
345
0
  }
346
347
29.2k
  return &intern->std;
348
29.2k
}
349
/* }}} */
350
351
/* {{{ spl_object_storage_clone */
352
static zend_object *spl_object_storage_clone(zend_object *old_object)
353
0
{
354
0
  zend_object *new_object;
355
356
0
  new_object = spl_object_storage_new_ex(old_object->ce, old_object);
357
358
0
  zend_objects_clone_members(new_object, old_object);
359
360
0
  return new_object;
361
0
}
362
/* }}} */
363
364
static inline HashTable* spl_object_storage_debug_info(zend_object *obj) /* {{{ */
365
0
{
366
0
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(obj);
367
0
  spl_SplObjectStorageElement *element;
368
0
  HashTable *props;
369
0
  zval tmp, storage;
370
0
  HashTable *debug_info;
371
372
0
  props = obj->handlers->get_properties(obj);
373
374
0
  debug_info = zend_new_array(zend_hash_num_elements(props) + 1);
375
0
  zend_hash_copy(debug_info, props, (copy_ctor_func_t)zval_add_ref);
376
377
0
  array_init(&storage);
378
379
0
  ZEND_HASH_FOREACH_PTR(&intern->storage, element) {
380
0
    array_init(&tmp);
381
0
    zval obj;
382
0
    ZVAL_OBJ_COPY(&obj, element->obj);
383
0
    add_assoc_zval_ex(&tmp, "obj", sizeof("obj") - 1, &obj);
384
0
    Z_TRY_ADDREF(element->inf);
385
0
    add_assoc_zval_ex(&tmp, "inf", sizeof("inf") - 1, &element->inf);
386
0
    zend_hash_next_index_insert(Z_ARRVAL(storage), &tmp);
387
0
  } ZEND_HASH_FOREACH_END();
388
389
0
  spl_set_private_debug_info_property(spl_ce_SplObjectStorage, "storage", strlen("storage"), debug_info, &storage);
390
391
0
  return debug_info;
392
0
}
393
/* }}} */
394
395
/* overridden for garbage collection */
396
static HashTable *spl_object_storage_get_gc(zend_object *obj, zval **table, int *n) /* {{{ */
397
65.8k
{
398
65.8k
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(obj);
399
65.8k
  spl_SplObjectStorageElement *element;
400
65.8k
  zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
401
402
78.0k
  ZEND_HASH_FOREACH_PTR(&intern->storage, element) {
403
78.0k
    zend_get_gc_buffer_add_obj(gc_buffer, element->obj);
404
78.0k
    zend_get_gc_buffer_add_zval(gc_buffer, &element->inf);
405
78.0k
  } ZEND_HASH_FOREACH_END();
406
407
65.8k
  zend_get_gc_buffer_use(gc_buffer, table, n);
408
65.8k
  return zend_std_get_properties(obj);
409
65.8k
}
410
/* }}} */
411
412
static int spl_object_storage_compare_info(zval *e1, zval *e2) /* {{{ */
413
0
{
414
0
  spl_SplObjectStorageElement *s1 = (spl_SplObjectStorageElement*)Z_PTR_P(e1);
415
0
  spl_SplObjectStorageElement *s2 = (spl_SplObjectStorageElement*)Z_PTR_P(e2);
416
417
0
  return zend_compare(&s1->inf, &s2->inf);
418
0
}
419
/* }}} */
420
421
static int spl_object_storage_compare_objects(zval *o1, zval *o2) /* {{{ */
422
0
{
423
0
  zend_object *zo1;
424
0
  zend_object *zo2;
425
426
0
  ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2);
427
428
0
  zo1 = (zend_object *)Z_OBJ_P(o1);
429
0
  zo2 = (zend_object *)Z_OBJ_P(o2);
430
431
0
  if (zo1->ce != spl_ce_SplObjectStorage || zo2->ce != spl_ce_SplObjectStorage) {
432
0
    return ZEND_UNCOMPARABLE;
433
0
  }
434
435
0
  return zend_hash_compare(&(Z_SPLOBJSTORAGE_P(o1))->storage, &(Z_SPLOBJSTORAGE_P(o2))->storage, (compare_func_t)spl_object_storage_compare_info, 0);
436
0
}
437
/* }}} */
438
439
/* {{{ spl_array_object_new */
440
static zend_object *spl_SplObjectStorage_new(zend_class_entry *class_type)
441
29.2k
{
442
29.2k
  return spl_object_storage_new_ex(class_type, NULL);
443
29.2k
}
444
/* }}} */
445
446
/* Returns true if the SplObjectStorage contains an entry for getHash(obj), even if the corresponding value is null. */
447
static bool spl_object_storage_contains(spl_SplObjectStorage *intern, zend_object *obj) /* {{{ */
448
34
{
449
34
  if (EXPECTED(!intern->fptr_get_hash)) {
450
34
    return zend_hash_index_find(&intern->storage, obj->handle) != NULL;
451
34
  }
452
0
  zend_hash_key key;
453
0
  if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) {
454
0
    return true;
455
0
  }
456
457
0
  ZEND_ASSERT(key.key);
458
0
  bool found = zend_hash_exists(&intern->storage, key.key);
459
0
  zend_string_release_ex(key.key, 0);
460
461
0
  return found;
462
0
} /* }}} */
463
464
/* {{{ Attaches an object to the storage if not yet contained */
465
PHP_METHOD(SplObjectStorage, attach)
466
0
{
467
0
  zend_object *obj;
468
0
  zval *inf = NULL;
469
470
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
471
472
0
  ZEND_PARSE_PARAMETERS_START(1, 2)
473
0
    Z_PARAM_OBJ(obj)
474
0
    Z_PARAM_OPTIONAL
475
0
    Z_PARAM_ZVAL(inf)
476
0
  ZEND_PARSE_PARAMETERS_END();
477
0
  spl_object_storage_attach(intern, obj, inf);
478
0
  if (UNEXPECTED(EG(exception))) {
479
0
    RETURN_THROWS();
480
0
  }
481
0
} /* }}} */
482
483
// todo: make spl_object_storage_has_dimension return bool as well
484
static int spl_object_storage_has_dimension(zend_object *object, zval *offset, int check_empty)
485
0
{
486
0
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
487
0
  if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) {
488
    /* Can't optimize empty()/isset() check if getHash, offsetExists, or offsetGet is overridden */
489
0
    return zend_std_has_dimension(object, offset, check_empty);
490
0
  }
491
0
  spl_SplObjectStorageElement *element = zend_hash_index_find_ptr(&intern->storage, Z_OBJ_HANDLE_P(offset));
492
0
  if (!element) {
493
0
    return 0;
494
0
  }
495
496
0
  if (check_empty) {
497
0
    return i_zend_is_true(&element->inf);
498
0
  }
499
  /* NOTE: SplObjectStorage->offsetExists() is an alias of SplObjectStorage->contains(), so this returns true even if the value is null. */
500
0
  return 1;
501
0
}
502
503
static zval *spl_object_storage_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
504
99
{
505
99
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
506
99
  if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) {
507
    /* Can't optimize it if getHash, offsetExists, or offsetGet is overridden */
508
2
    return zend_std_read_dimension(object, offset, type, rv);
509
2
  }
510
97
  spl_SplObjectStorageElement *element = zend_hash_index_find_ptr(&intern->storage, Z_OBJ_HANDLE_P(offset));
511
512
97
  if (!element) {
513
3
    if (type == BP_VAR_IS) {
514
0
      return &EG(uninitialized_zval);
515
0
    }
516
3
    zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Object not found");
517
3
    return NULL;
518
94
  } else {
519
    /* This deliberately returns a non-reference, even for BP_VAR_W and BP_VAR_RW, to behave the same way as SplObjectStorage did when using the default zend_std_read_dimension behavior.
520
     * i.e. This prevents taking a reference to an entry of SplObjectStorage because offsetGet would return a non-reference. */
521
94
    ZVAL_COPY_DEREF(rv, &element->inf);
522
94
    return rv;
523
94
  }
524
97
}
525
526
static void spl_object_storage_write_dimension(zend_object *object, zval *offset, zval *inf)
527
93
{
528
93
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
529
93
  if (UNEXPECTED(spl_object_storage_is_mutating_within_get_hash_call())) {
530
0
    return;
531
0
  }
532
533
93
  if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION))) {
534
0
    zend_std_write_dimension(object, offset, inf);
535
0
    return;
536
0
  }
537
93
  spl_object_storage_attach_handle(intern, Z_OBJ_P(offset), inf);
538
93
}
539
540
static void spl_multiple_iterator_write_dimension(zend_object *object, zval *offset, zval *inf)
541
0
{
542
0
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
543
0
  if (UNEXPECTED(spl_object_storage_is_mutating_within_get_hash_call())) {
544
0
    return;
545
0
  }
546
547
0
  if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION))) {
548
0
    zend_std_write_dimension(object, offset, inf);
549
0
    return;
550
0
  }
551
0
  if (UNEXPECTED(!Z_OBJCE_P(offset)->iterator_funcs_ptr || !Z_OBJCE_P(offset)->iterator_funcs_ptr->zf_valid)) {
552
0
    zend_type_error("Can only attach objects that implement the Iterator interface");
553
0
    return;
554
0
  }
555
0
  spl_object_storage_attach_handle(intern, Z_OBJ_P(offset), inf);
556
0
}
557
558
static void spl_object_storage_unset_dimension(zend_object *object, zval *offset)
559
0
{
560
0
  spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
561
0
  if (UNEXPECTED(spl_object_storage_is_mutating_within_get_hash_call())) {
562
0
    return;
563
0
  }
564
565
0
  if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_UNSET_DIMENSION))) {
566
0
    zend_std_unset_dimension(object, offset);
567
0
    return;
568
0
  }
569
0
  zend_hash_index_del(&intern->storage, Z_OBJ_HANDLE_P(offset));
570
0
}
571
572
/* {{{ Detaches an object from the storage */
573
PHP_METHOD(SplObjectStorage, detach)
574
0
{
575
0
  zend_object *obj;
576
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
577
578
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
579
0
    Z_PARAM_OBJ(obj)
580
0
  ZEND_PARSE_PARAMETERS_END();
581
0
  spl_object_storage_detach(intern, obj);
582
0
  if (UNEXPECTED(EG(exception))) {
583
0
    RETURN_THROWS();
584
0
  }
585
586
0
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
587
0
  intern->index = 0;
588
0
} /* }}} */
589
590
/* {{{ Returns the hash of an object */
591
PHP_METHOD(SplObjectStorage, getHash)
592
0
{
593
0
  zend_object *obj;
594
595
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
596
0
    Z_PARAM_OBJ(obj)
597
0
  ZEND_PARSE_PARAMETERS_END();
598
599
0
  RETURN_NEW_STR(php_spl_object_hash(obj));
600
601
0
} /* }}} */
602
603
/* {{{ Returns associated information for a stored object */
604
PHP_METHOD(SplObjectStorage, offsetGet)
605
2
{
606
2
  zend_object *obj;
607
2
  spl_SplObjectStorageElement *element;
608
2
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
609
2
  zend_hash_key key;
610
611
6
  ZEND_PARSE_PARAMETERS_START(1, 1)
612
8
    Z_PARAM_OBJ(obj)
613
2
  ZEND_PARSE_PARAMETERS_END();
614
615
0
  if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) {
616
    /* This may be the old NULL fallback, or an exception thrown by getHash(). */
617
0
    RETURN_NULL();
618
0
  }
619
620
0
  element = spl_object_storage_get(intern, &key);
621
0
  spl_object_storage_free_hash(intern, &key);
622
623
0
  if (!element) {
624
0
    zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Object not found");
625
0
  } else {
626
0
    RETURN_COPY_DEREF(&element->inf);
627
0
  }
628
0
} /* }}} */
629
630
/* {{{ Add all elements contained in $os */
631
PHP_METHOD(SplObjectStorage, addAll)
632
0
{
633
0
  zval *obj;
634
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
635
0
  spl_SplObjectStorage *other;
636
637
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) {
638
0
    RETURN_THROWS();
639
0
  }
640
641
0
  other = Z_SPLOBJSTORAGE_P(obj);
642
643
0
  if (UNEXPECTED(spl_object_storage_addall(intern, other) == FAILURE)) {
644
0
    RETURN_THROWS();
645
0
  }
646
647
0
  RETURN_LONG(zend_hash_num_elements(&intern->storage));
648
0
} /* }}} */
649
650
/* {{{ Remove all elements contained in $os */
651
PHP_METHOD(SplObjectStorage, removeAll)
652
0
{
653
0
  zval *obj;
654
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
655
0
  spl_SplObjectStorage *other;
656
0
  spl_SplObjectStorageElement *element;
657
658
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) {
659
0
    RETURN_THROWS();
660
0
  }
661
662
0
  other = Z_SPLOBJSTORAGE_P(obj);
663
664
0
  zend_hash_internal_pointer_reset(&other->storage);
665
0
  while ((element = zend_hash_get_current_data_ptr(&other->storage)) != NULL) {
666
0
    if (spl_object_storage_detach(intern, element->obj) == FAILURE) {
667
0
      if (UNEXPECTED(EG(exception))) {
668
0
        RETURN_THROWS();
669
0
      }
670
0
      zend_hash_move_forward(&other->storage);
671
0
    }
672
0
  }
673
674
0
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
675
0
  intern->index = 0;
676
677
0
  RETURN_LONG(zend_hash_num_elements(&intern->storage));
678
0
} /* }}} */
679
680
/* {{{ Remove elements not common to both this SplObjectStorage instance and $os */
681
PHP_METHOD(SplObjectStorage, removeAllExcept)
682
0
{
683
0
  zval *obj;
684
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
685
0
  spl_SplObjectStorage *other;
686
0
  spl_SplObjectStorageElement *element;
687
688
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) {
689
0
    RETURN_THROWS();
690
0
  }
691
692
0
  other = Z_SPLOBJSTORAGE_P(obj);
693
694
0
  SPL_SAFE_HASH_FOREACH_PTR(&intern->storage, element) {
695
0
    zend_object *elem_obj = element->obj;
696
0
    GC_ADDREF(elem_obj);
697
0
    bool contains = spl_object_storage_contains(other, elem_obj);
698
0
    if (UNEXPECTED(EG(exception))) {
699
0
      OBJ_RELEASE(elem_obj);
700
0
      RETURN_THROWS();
701
0
    }
702
0
    if (!contains) {
703
0
      if (spl_object_storage_detach(intern, elem_obj) == FAILURE) {
704
0
        OBJ_RELEASE(elem_obj);
705
0
        if (UNEXPECTED(EG(exception))) {
706
0
          RETURN_THROWS();
707
0
        }
708
0
        continue;
709
0
      }
710
0
    }
711
0
    OBJ_RELEASE(elem_obj);
712
0
  } ZEND_HASH_FOREACH_END();
713
714
0
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
715
0
  intern->index = 0;
716
717
0
  RETURN_LONG(zend_hash_num_elements(&intern->storage));
718
0
}
719
/* }}} */
720
721
/* {{{ Determine whether an object is contained in the storage */
722
PHP_METHOD(SplObjectStorage, contains)
723
34
{
724
34
  zend_object *obj;
725
34
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
726
727
102
  ZEND_PARSE_PARAMETERS_START(1, 1)
728
136
    Z_PARAM_OBJ(obj)
729
34
  ZEND_PARSE_PARAMETERS_END();
730
731
34
  bool contains = spl_object_storage_contains(intern, obj);
732
34
  if (UNEXPECTED(EG(exception))) {
733
0
    RETURN_THROWS();
734
0
  }
735
736
34
  RETURN_BOOL(contains);
737
34
} /* }}} */
738
739
/* {{{ Determine number of objects in storage */
740
PHP_METHOD(SplObjectStorage, count)
741
0
{
742
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
743
0
  zend_long mode = PHP_COUNT_NORMAL;
744
745
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &mode) == FAILURE) {
746
0
    RETURN_THROWS();
747
0
  }
748
749
0
  if (mode == PHP_COUNT_RECURSIVE) {
750
0
    RETURN_LONG(php_count_recursive(&intern->storage));
751
0
  }
752
753
0
  RETURN_LONG(zend_hash_num_elements(&intern->storage));
754
0
} /* }}} */
755
756
/* {{{ Rewind to first position */
757
PHP_METHOD(SplObjectStorage, rewind)
758
0
{
759
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
760
761
0
  ZEND_PARSE_PARAMETERS_NONE();
762
763
0
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
764
0
  intern->index = 0;
765
0
} /* }}} */
766
767
/* {{{ Returns whether current position is valid */
768
PHP_METHOD(SplObjectStorage, valid)
769
0
{
770
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
771
772
0
  ZEND_PARSE_PARAMETERS_NONE();
773
774
0
  RETURN_BOOL(zend_hash_has_more_elements_ex(&intern->storage, &intern->pos) == SUCCESS);
775
0
} /* }}} */
776
777
/* {{{ Returns current key */
778
PHP_METHOD(SplObjectStorage, key)
779
0
{
780
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
781
782
0
  ZEND_PARSE_PARAMETERS_NONE();
783
784
0
  RETURN_LONG(intern->index);
785
0
} /* }}} */
786
787
/* {{{ Returns current element */
788
PHP_METHOD(SplObjectStorage, current)
789
0
{
790
0
  spl_SplObjectStorageElement *element;
791
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
792
793
0
  ZEND_PARSE_PARAMETERS_NONE();
794
795
0
  if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) == NULL) {
796
0
    zend_throw_exception(spl_ce_RuntimeException, "Called current() on invalid iterator", 0);
797
0
    RETURN_THROWS();
798
0
  }
799
0
  ZVAL_OBJ_COPY(return_value, element->obj);
800
0
} /* }}} */
801
802
/* {{{ Returns associated information to current element */
803
PHP_METHOD(SplObjectStorage, getInfo)
804
0
{
805
0
  spl_SplObjectStorageElement *element;
806
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
807
808
0
  ZEND_PARSE_PARAMETERS_NONE();
809
810
0
  if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) == NULL) {
811
0
    RETURN_NULL();
812
0
  }
813
0
  ZVAL_COPY(return_value, &element->inf);
814
0
} /* }}} */
815
816
/* {{{ Sets associated information of current element to $inf */
817
PHP_METHOD(SplObjectStorage, setInfo)
818
0
{
819
0
  spl_SplObjectStorageElement *element;
820
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
821
0
  zval *inf;
822
823
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &inf) == FAILURE) {
824
0
    RETURN_THROWS();
825
0
  }
826
0
  if (UNEXPECTED(spl_object_storage_is_mutating_within_get_hash_call())) {
827
0
    RETURN_THROWS();
828
0
  }
829
830
0
  if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) == NULL) {
831
0
    RETURN_NULL();
832
0
  }
833
0
  zval garbage;
834
0
  ZVAL_COPY_VALUE(&garbage, &element->inf);
835
0
  ZVAL_COPY(&element->inf, inf);
836
0
  zval_ptr_dtor(&garbage);
837
0
} /* }}} */
838
839
/* {{{ Moves position forward */
840
PHP_METHOD(SplObjectStorage, next)
841
0
{
842
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
843
844
0
  ZEND_PARSE_PARAMETERS_NONE();
845
846
0
  zend_hash_move_forward_ex(&intern->storage, &intern->pos);
847
0
  intern->index++;
848
0
} /* }}} */
849
850
/* {{{ Seek to position. */
851
PHP_METHOD(SplObjectStorage, seek)
852
0
{
853
0
  zend_long position;
854
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
855
856
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &position) == FAILURE) {
857
0
    RETURN_THROWS();
858
0
  }
859
860
0
  if (position < 0 || position >= zend_hash_num_elements(&intern->storage)) {
861
0
    zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Seek position " ZEND_LONG_FMT " is out of range", position);
862
0
    RETURN_THROWS();
863
0
  }
864
865
0
  if (position == 0) {
866
    /* fast path */
867
0
    zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
868
0
    intern->index = 0;
869
0
  } else if (position > intern->index) {
870
    /* unlike the optimization below, it's not cheap to go to the end */
871
0
    do {
872
0
      zend_hash_move_forward_ex(&intern->storage, &intern->pos);
873
0
      intern->index++;
874
0
    } while (position > intern->index);
875
0
  } else if (position < intern->index) {
876
    /* optimization: check if it's more profitable to reset and do a forwards seek instead, it's cheap to reset */
877
0
    if (intern->index - position > position) {
878
0
      zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
879
0
      intern->index = 0;
880
0
      do {
881
0
        zend_hash_move_forward_ex(&intern->storage, &intern->pos);
882
0
        intern->index++;
883
0
      } while (position > intern->index);
884
0
    } else {
885
0
      do {
886
0
        zend_hash_move_backwards_ex(&intern->storage, &intern->pos);
887
0
        intern->index--;
888
0
      } while (position < intern->index);
889
0
    }
890
0
  }
891
0
} /* }}} */
892
893
/* {{{ Serializes storage */
894
PHP_METHOD(SplObjectStorage, serialize)
895
0
{
896
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
897
898
0
  spl_SplObjectStorageElement *element;
899
0
  zval members, flags;
900
0
  HashPosition      pos;
901
0
  php_serialize_data_t var_hash;
902
0
  smart_str buf = {0};
903
904
0
  ZEND_PARSE_PARAMETERS_NONE();
905
906
0
  PHP_VAR_SERIALIZE_INIT(var_hash);
907
908
  /* storage */
909
0
  smart_str_appendl(&buf, "x:", 2);
910
0
  ZVAL_LONG(&flags, zend_hash_num_elements(&intern->storage));
911
0
  php_var_serialize(&buf, &flags, &var_hash);
912
913
0
  zend_hash_internal_pointer_reset_ex(&intern->storage, &pos);
914
915
0
  while (zend_hash_has_more_elements_ex(&intern->storage, &pos) == SUCCESS) {
916
0
    zval obj;
917
0
    if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &pos)) == NULL) {
918
0
      smart_str_free(&buf);
919
0
      PHP_VAR_SERIALIZE_DESTROY(var_hash);
920
0
      RETURN_NULL();
921
0
    }
922
0
    ZVAL_OBJ(&obj, element->obj);
923
924
    /* Protect against modification; we need a full copy because the data may be refcounted. */
925
0
    zval inf_copy;
926
0
    ZVAL_COPY(&inf_copy, &element->inf);
927
928
0
    php_var_serialize(&buf, &obj, &var_hash);
929
0
    smart_str_appendc(&buf, ',');
930
0
    php_var_serialize(&buf, &inf_copy, &var_hash);
931
0
    smart_str_appendc(&buf, ';');
932
0
    zend_hash_move_forward_ex(&intern->storage, &pos);
933
934
0
    zval_ptr_dtor(&inf_copy);
935
0
  }
936
937
  /* members */
938
0
  smart_str_appendl(&buf, "m:", 2);
939
940
0
  ZVAL_ARR(&members, zend_array_dup(zend_std_get_properties(Z_OBJ_P(ZEND_THIS))));
941
0
  php_var_serialize(&buf, &members, &var_hash); /* finishes the string */
942
0
  zval_ptr_dtor(&members);
943
944
  /* done */
945
0
  PHP_VAR_SERIALIZE_DESTROY(var_hash);
946
947
0
  RETURN_STR(smart_str_extract(&buf));
948
0
} /* }}} */
949
950
/* {{{ Unserializes storage */
951
PHP_METHOD(SplObjectStorage, unserialize)
952
161
{
953
161
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
954
955
161
  char *buf;
956
161
  size_t buf_len;
957
161
  const unsigned char *p, *s;
958
161
  php_unserialize_data_t var_hash;
959
161
  zval *pcount, *pmembers;
960
161
  spl_SplObjectStorageElement *element;
961
161
  zend_long count;
962
963
161
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &buf, &buf_len) == FAILURE) {
964
0
    RETURN_THROWS();
965
0
  }
966
967
161
  if (buf_len == 0) {
968
1
    return;
969
1
  }
970
971
  /* storage */
972
160
  s = p = (const unsigned char*)buf;
973
160
  PHP_VAR_UNSERIALIZE_INIT(var_hash);
974
975
160
  if (*p!= 'x' || *++p != ':') {
976
4
    goto outexcept;
977
4
  }
978
156
  ++p;
979
980
156
  pcount = var_tmp_var(&var_hash);
981
156
  if (!php_var_unserialize(pcount, &p, s + buf_len, &var_hash) || Z_TYPE_P(pcount) != IS_LONG) {
982
2
    goto outexcept;
983
2
  }
984
985
154
  --p; /* for ';' */
986
154
  count = Z_LVAL_P(pcount);
987
154
  if (count < 0) {
988
55
    goto outexcept;
989
55
  }
990
991
180
  while (count-- > 0) {
992
159
    spl_SplObjectStorageElement *pelement;
993
159
    zend_hash_key key;
994
159
    zval *entry = var_tmp_var(&var_hash);
995
159
    zval inf;
996
159
    ZVAL_UNDEF(&inf);
997
998
159
    if (*p != ';') {
999
7
      goto outexcept;
1000
7
    }
1001
152
    ++p;
1002
152
    if(*p != 'O' && *p != 'C' && *p != 'r') {
1003
40
      goto outexcept;
1004
40
    }
1005
    /* store reference to allow cross-references between different elements */
1006
112
    if (!php_var_unserialize(entry, &p, s + buf_len, &var_hash)) {
1007
29
      goto outexcept;
1008
29
    }
1009
83
    if (*p == ',') { /* new version has inf */
1010
16
      ++p;
1011
16
      if (!php_var_unserialize(&inf, &p, s + buf_len, &var_hash)) {
1012
1
        zval_ptr_dtor(&inf);
1013
1
        goto outexcept;
1014
1
      }
1015
16
    }
1016
82
    if (Z_TYPE_P(entry) != IS_OBJECT) {
1017
1
      zval_ptr_dtor(&inf);
1018
1
      goto outexcept;
1019
1
    }
1020
1021
81
    if (spl_object_storage_get_hash(&key, intern, Z_OBJ_P(entry)) == FAILURE) {
1022
0
      zval_ptr_dtor(&inf);
1023
0
      if (EG(exception)) {
1024
0
        PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1025
0
        RETURN_THROWS();
1026
0
      }
1027
0
      goto outexcept;
1028
0
    }
1029
81
    pelement = spl_object_storage_get(intern, &key);
1030
81
    spl_object_storage_free_hash(intern, &key);
1031
81
    if (pelement) {
1032
35
      zval obj;
1033
35
      if (!Z_ISUNDEF(pelement->inf)) {
1034
35
        var_push_dtor(&var_hash, &pelement->inf);
1035
35
      }
1036
35
      ZVAL_OBJ(&obj, pelement->obj);
1037
35
      var_push_dtor(&var_hash, &obj);
1038
35
    }
1039
81
    element = spl_object_storage_attach(intern, Z_OBJ_P(entry), Z_ISUNDEF(inf)?NULL:&inf);
1040
81
    if (UNEXPECTED(!element)) {
1041
0
      zval_ptr_dtor(&inf);
1042
0
      if (EG(exception)) {
1043
0
        PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1044
0
        RETURN_THROWS();
1045
0
      }
1046
0
      goto outexcept;
1047
0
    }
1048
81
    var_replace(&var_hash, &inf, &element->inf);
1049
81
    zval_ptr_dtor(&inf);
1050
81
  }
1051
1052
21
  if (*p != ';') {
1053
1
    goto outexcept;
1054
1
  }
1055
20
  ++p;
1056
1057
  /* members */
1058
20
  if (*p!= 'm' || *++p != ':') {
1059
4
    goto outexcept;
1060
4
  }
1061
16
  ++p;
1062
1063
16
  pmembers = var_tmp_var(&var_hash);
1064
16
  if (!php_var_unserialize(pmembers, &p, s + buf_len, &var_hash) || Z_TYPE_P(pmembers) != IS_ARRAY) {
1065
15
    goto outexcept;
1066
15
  }
1067
1068
  /* copy members */
1069
1
  object_properties_load(&intern->std, Z_ARRVAL_P(pmembers));
1070
1071
1
  PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1072
1
  return;
1073
1074
159
outexcept:
1075
159
  PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1076
159
  zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Error at offset %zd of %zd bytes", ((char*)p - buf), buf_len);
1077
159
  RETURN_THROWS();
1078
1079
159
} /* }}} */
1080
1081
/* {{{ */
1082
PHP_METHOD(SplObjectStorage, __serialize)
1083
13
{
1084
13
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1085
13
  spl_SplObjectStorageElement *elem;
1086
13
  zval tmp;
1087
1088
13
  ZEND_PARSE_PARAMETERS_NONE();
1089
1090
13
  array_init(return_value);
1091
1092
  /* storage */
1093
13
  array_init_size(&tmp, 2 * zend_hash_num_elements(&intern->storage));
1094
91
  ZEND_HASH_FOREACH_PTR(&intern->storage, elem) {
1095
91
    zval obj;
1096
91
    ZVAL_OBJ_COPY(&obj, elem->obj);
1097
91
    zend_hash_next_index_insert(Z_ARRVAL(tmp), &obj);
1098
91
    Z_TRY_ADDREF(elem->inf);
1099
91
    zend_hash_next_index_insert(Z_ARRVAL(tmp), &elem->inf);
1100
91
  } ZEND_HASH_FOREACH_END();
1101
13
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1102
1103
  /* members */
1104
13
  ZVAL_ARR(&tmp, zend_proptable_to_symtable(
1105
13
    zend_std_get_properties(&intern->std), /* always_duplicate */ 1));
1106
13
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1107
13
} /* }}} */
1108
1109
/* {{{ */
1110
PHP_METHOD(SplObjectStorage, __unserialize)
1111
23.7k
{
1112
23.7k
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1113
23.7k
  HashTable *data;
1114
23.7k
  zval *storage_zv, *members_zv, *key, *val;
1115
1116
23.7k
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
1117
0
    RETURN_THROWS();
1118
0
  }
1119
1120
23.7k
  storage_zv = zend_hash_index_find(data, 0);
1121
23.7k
  members_zv = zend_hash_index_find(data, 1);
1122
23.7k
  if (!storage_zv || !members_zv ||
1123
23.7k
      Z_TYPE_P(storage_zv) != IS_ARRAY || Z_TYPE_P(members_zv) != IS_ARRAY) {
1124
57
    zend_throw_exception(spl_ce_UnexpectedValueException,
1125
57
      "Incomplete or ill-typed serialization data", 0);
1126
57
    RETURN_THROWS();
1127
57
  }
1128
1129
23.7k
  if (zend_hash_num_elements(Z_ARRVAL_P(storage_zv)) % 2 != 0) {
1130
1
    zend_throw_exception(spl_ce_UnexpectedValueException, "Odd number of elements", 0);
1131
1
    RETURN_THROWS();
1132
1
  }
1133
1134
23.7k
  key = NULL;
1135
30.1k
  ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(storage_zv), val) {
1136
30.1k
    if (key) {
1137
1.59k
      if (Z_TYPE_P(key) != IS_OBJECT) {
1138
2
        zend_throw_exception(spl_ce_UnexpectedValueException, "Non-object key", 0);
1139
2
        RETURN_THROWS();
1140
2
      }
1141
1142
1.59k
      ZVAL_DEREF(val);
1143
1.59k
      if (UNEXPECTED(!spl_object_storage_attach(intern, Z_OBJ_P(key), val))) {
1144
0
        RETURN_THROWS();
1145
0
      }
1146
1.59k
      key = NULL;
1147
1.59k
    } else {
1148
1.59k
      key = val;
1149
1.59k
    }
1150
30.1k
  } ZEND_HASH_FOREACH_END();
1151
1152
23.7k
  object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
1153
23.7k
}
1154
1155
/* {{{ */
1156
PHP_METHOD(SplObjectStorage, __debugInfo)
1157
0
{
1158
0
  ZEND_PARSE_PARAMETERS_NONE();
1159
1160
0
  RETURN_ARR(spl_object_storage_debug_info(Z_OBJ_P(ZEND_THIS)));
1161
0
}
1162
/* }}} */
1163
1164
96
#define SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT   1
1165
0
#define SPL_MULTIPLE_ITERATOR_GET_ALL_KEY       2
1166
1167
/* {{{ Iterator that iterates over several iterators one after the other */
1168
PHP_METHOD(MultipleIterator, __construct)
1169
28
{
1170
28
  spl_SplObjectStorage   *intern;
1171
28
  zend_long               flags = MIT_NEED_ALL|MIT_KEYS_NUMERIC;
1172
1173
28
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &flags) == FAILURE) {
1174
0
    RETURN_THROWS();
1175
0
  }
1176
1177
28
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1178
28
  intern->flags = flags;
1179
28
}
1180
/* }}} */
1181
1182
/* {{{ Return current flags */
1183
PHP_METHOD(MultipleIterator, getFlags)
1184
0
{
1185
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1186
1187
0
  ZEND_PARSE_PARAMETERS_NONE();
1188
0
  RETURN_LONG(intern->flags);
1189
0
}
1190
/* }}} */
1191
1192
/* {{{ Set flags */
1193
PHP_METHOD(MultipleIterator, setFlags)
1194
0
{
1195
0
  spl_SplObjectStorage *intern;
1196
0
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1197
1198
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &intern->flags) == FAILURE) {
1199
0
    RETURN_THROWS();
1200
0
  }
1201
0
}
1202
/* }}} */
1203
1204
/* {{{ Attach a new iterator */
1205
PHP_METHOD(MultipleIterator, attachIterator)
1206
47
{
1207
47
  spl_SplObjectStorage *intern;
1208
47
  zend_object *iterator = NULL;
1209
47
  zval zinfo;
1210
47
  zend_string *info_str;
1211
47
  zend_long info_long;
1212
47
  bool info_is_null = 1;
1213
1214
141
  ZEND_PARSE_PARAMETERS_START(1, 2)
1215
188
    Z_PARAM_OBJ_OF_CLASS(iterator, zend_ce_iterator)
1216
47
    Z_PARAM_OPTIONAL
1217
94
    Z_PARAM_STR_OR_LONG_OR_NULL(info_str, info_long, info_is_null)
1218
94
  ZEND_PARSE_PARAMETERS_END();
1219
1220
47
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1221
1222
47
  if (!info_is_null) {
1223
0
    spl_SplObjectStorageElement *element;
1224
1225
0
    if (info_str) {
1226
0
      ZVAL_STR(&zinfo, info_str);
1227
0
    } else {
1228
0
      ZVAL_LONG(&zinfo, info_long);
1229
0
    }
1230
1231
0
    zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1232
0
    while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL) {
1233
0
      if (fast_is_identical_function(&zinfo, &element->inf)) {
1234
0
        zend_throw_exception(spl_ce_InvalidArgumentException, "Key duplication error", 0);
1235
0
        RETURN_THROWS();
1236
0
      }
1237
0
      zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1238
0
    }
1239
1240
0
    spl_object_storage_attach(intern, iterator, &zinfo);
1241
0
    if (UNEXPECTED(EG(exception))) {
1242
0
      RETURN_THROWS();
1243
0
    }
1244
47
  } else {
1245
47
    spl_object_storage_attach(intern, iterator, NULL);
1246
47
    if (UNEXPECTED(EG(exception))) {
1247
0
      RETURN_THROWS();
1248
0
    }
1249
47
  }
1250
47
}
1251
/* }}} */
1252
1253
/* {{{ Detaches an iterator */
1254
PHP_METHOD(MultipleIterator, detachIterator)
1255
0
{
1256
0
  zval *iterator;
1257
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1258
1259
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &iterator, zend_ce_iterator) == FAILURE) {
1260
0
    RETURN_THROWS();
1261
0
  }
1262
0
  spl_object_storage_detach(intern, Z_OBJ_P(iterator));
1263
0
  if (UNEXPECTED(EG(exception))) {
1264
0
    RETURN_THROWS();
1265
0
  }
1266
1267
0
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1268
0
  intern->index = 0;
1269
0
} /* }}} */
1270
1271
/* {{{ Determine whether the iterator exists */
1272
PHP_METHOD(MultipleIterator, containsIterator)
1273
0
{
1274
0
  zval *iterator;
1275
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1276
1277
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &iterator, zend_ce_iterator) == FAILURE) {
1278
0
    RETURN_THROWS();
1279
0
  }
1280
0
  RETURN_BOOL(spl_object_storage_contains(intern, Z_OBJ_P(iterator)));
1281
0
} /* }}} */
1282
1283
PHP_METHOD(MultipleIterator, countIterators)
1284
0
{
1285
0
  spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1286
1287
0
  ZEND_PARSE_PARAMETERS_NONE();
1288
1289
0
  RETURN_LONG(zend_hash_num_elements(&intern->storage));
1290
0
}
1291
1292
/* {{{ Rewind all attached iterator instances */
1293
PHP_METHOD(MultipleIterator, rewind)
1294
20
{
1295
20
  spl_SplObjectStorage        *intern;
1296
20
  spl_SplObjectStorageElement *element;
1297
1298
20
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1299
1300
20
  ZEND_PARSE_PARAMETERS_NONE();
1301
1302
20
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1303
58
  while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL && !EG(exception)) {
1304
38
    zend_object *it = element->obj;
1305
38
    GC_ADDREF(it);
1306
38
    zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_rewind, it, NULL);
1307
38
    OBJ_RELEASE(it);
1308
38
    zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1309
38
  }
1310
20
}
1311
/* }}} */
1312
1313
/* {{{ Move all attached iterator instances forward */
1314
PHP_METHOD(MultipleIterator, next)
1315
28
{
1316
28
  spl_SplObjectStorage        *intern;
1317
28
  spl_SplObjectStorageElement *element;
1318
1319
28
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1320
1321
28
  ZEND_PARSE_PARAMETERS_NONE();
1322
1323
28
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1324
84
  while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL && !EG(exception)) {
1325
56
    zend_object *it = element->obj;
1326
56
    GC_ADDREF(it);
1327
56
    zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_next, it, NULL);
1328
56
    OBJ_RELEASE(it);
1329
56
    zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1330
56
  }
1331
28
}
1332
/* }}} */
1333
1334
/* {{{ Return whether all or one sub iterator is valid depending on flags */
1335
PHP_METHOD(MultipleIterator, valid)
1336
46
{
1337
46
  spl_SplObjectStorage        *intern;
1338
46
  spl_SplObjectStorageElement *element;
1339
46
  zval                         retval;
1340
46
  zend_long                         expect, valid;
1341
1342
46
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1343
1344
46
  ZEND_PARSE_PARAMETERS_NONE();
1345
1346
46
  if (!zend_hash_num_elements(&intern->storage)) {
1347
0
    RETURN_FALSE;
1348
0
  }
1349
1350
46
  expect = (intern->flags & MIT_NEED_ALL) ? 1 : 0;
1351
1352
46
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1353
110
  while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL && !EG(exception)) {
1354
78
    zend_object *it = element->obj;
1355
78
    GC_ADDREF(it);
1356
78
    zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_valid, it, &retval);
1357
78
    OBJ_RELEASE(it);
1358
1359
78
    if (!Z_ISUNDEF(retval)) {
1360
78
      valid = (Z_TYPE(retval) == IS_TRUE);
1361
78
      zval_ptr_dtor(&retval);
1362
78
    } else {
1363
0
      valid = 0;
1364
0
    }
1365
1366
78
    if (expect != valid) {
1367
14
      RETURN_BOOL(!expect);
1368
14
    }
1369
1370
64
    zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1371
64
  }
1372
1373
32
  RETURN_BOOL(expect);
1374
32
}
1375
/* }}} */
1376
1377
static void spl_multiple_iterator_get_all(spl_SplObjectStorage *intern, int get_type, zval *return_value) /* {{{ */
1378
32
{
1379
32
  spl_SplObjectStorageElement *element;
1380
32
  zval                         retval;
1381
32
  int                          valid = 1, num_elements;
1382
1383
32
  num_elements = zend_hash_num_elements(&intern->storage);
1384
32
  if (num_elements < 1) {
1385
0
    zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Called %s() on an invalid iterator",
1386
0
      get_type == SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT ? "current" : "key");
1387
0
    RETURN_THROWS();
1388
0
  }
1389
1390
32
  array_init_size(return_value, num_elements);
1391
1392
32
  zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1393
96
  while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL && !EG(exception)) {
1394
64
    zend_object *it = element->obj;
1395
64
    zval inf;
1396
64
    GC_ADDREF(it);
1397
64
    ZVAL_COPY(&inf, &element->inf);
1398
64
    zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_valid, it, &retval);
1399
1400
64
    if (!Z_ISUNDEF(retval)) {
1401
64
      valid = Z_TYPE(retval) == IS_TRUE;
1402
64
      zval_ptr_dtor(&retval);
1403
64
    } else {
1404
0
      valid = 0;
1405
0
    }
1406
1407
64
    if (valid) {
1408
64
      if (SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT == get_type) {
1409
64
        zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_current, it, &retval);
1410
64
      } else {
1411
0
        zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_key, it, &retval);
1412
0
      }
1413
64
      if (Z_ISUNDEF(retval)) {
1414
0
        OBJ_RELEASE(it);
1415
0
        zval_ptr_dtor(&inf);
1416
0
        zend_throw_exception(spl_ce_RuntimeException, "Failed to call sub iterator method", 0);
1417
0
        return;
1418
0
      }
1419
64
    } else if (intern->flags & MIT_NEED_ALL) {
1420
0
      OBJ_RELEASE(it);
1421
0
      zval_ptr_dtor(&inf);
1422
0
      if (SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT == get_type) {
1423
0
        zend_throw_exception(spl_ce_RuntimeException, "Called current() with non valid sub iterator", 0);
1424
0
      } else {
1425
0
        zend_throw_exception(spl_ce_RuntimeException, "Called key() with non valid sub iterator", 0);
1426
0
      }
1427
0
      return;
1428
0
    } else {
1429
0
      ZVAL_NULL(&retval);
1430
0
    }
1431
1432
64
    if (intern->flags & MIT_KEYS_ASSOC) {
1433
0
      switch (Z_TYPE(inf)) {
1434
0
        case IS_LONG:
1435
0
          add_index_zval(return_value, Z_LVAL(inf), &retval);
1436
0
          break;
1437
0
        case IS_STRING:
1438
0
          zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR(inf), &retval);
1439
0
          break;
1440
0
        default:
1441
0
          zval_ptr_dtor(&retval);
1442
0
          OBJ_RELEASE(it);
1443
0
          zval_ptr_dtor(&inf);
1444
0
          zend_throw_exception(spl_ce_InvalidArgumentException, "Sub-Iterator is associated with NULL", 0);
1445
0
          return;
1446
0
      }
1447
64
    } else {
1448
64
      add_next_index_zval(return_value, &retval);
1449
64
    }
1450
1451
64
    OBJ_RELEASE(it);
1452
64
    zval_ptr_dtor(&inf);
1453
64
    zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1454
64
  }
1455
32
}
1456
/* }}} */
1457
1458
/* {{{ Return an array of all registered Iterator instances current() result */
1459
PHP_METHOD(MultipleIterator, current)
1460
32
{
1461
32
  spl_SplObjectStorage        *intern;
1462
32
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1463
1464
32
  ZEND_PARSE_PARAMETERS_NONE();
1465
1466
32
  spl_multiple_iterator_get_all(intern, SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT, return_value);
1467
32
}
1468
/* }}} */
1469
1470
/* {{{ Return an array of all registered Iterator instances key() result */
1471
PHP_METHOD(MultipleIterator, key)
1472
0
{
1473
0
  spl_SplObjectStorage *intern;
1474
0
  intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1475
1476
0
  ZEND_PARSE_PARAMETERS_NONE();
1477
1478
0
  spl_multiple_iterator_get_all(intern, SPL_MULTIPLE_ITERATOR_GET_ALL_KEY, return_value);
1479
0
}
1480
/* }}} */
1481
1482
/* {{{ PHP_MINIT_FUNCTION(spl_observer) */
1483
PHP_MINIT_FUNCTION(spl_observer)
1484
16
{
1485
16
  spl_ce_SplObserver = register_class_SplObserver();
1486
16
  spl_ce_SplSubject = register_class_SplSubject();
1487
1488
16
  spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, spl_ce_SeekableIterator, zend_ce_serializable, zend_ce_arrayaccess);
1489
16
  spl_ce_SplObjectStorage->create_object = spl_SplObjectStorage_new;
1490
16
  spl_ce_SplObjectStorage->default_object_handlers = &spl_handler_SplObjectStorage;
1491
1492
16
  memcpy(&spl_handler_SplObjectStorage, &std_object_handlers, sizeof(zend_object_handlers));
1493
1494
16
  spl_handler_SplObjectStorage.offset          = offsetof(spl_SplObjectStorage, std);
1495
16
  spl_handler_SplObjectStorage.compare         = spl_object_storage_compare_objects;
1496
16
  spl_handler_SplObjectStorage.clone_obj       = spl_object_storage_clone;
1497
16
  spl_handler_SplObjectStorage.get_gc          = spl_object_storage_get_gc;
1498
16
  spl_handler_SplObjectStorage.free_obj        = spl_SplObjectStorage_free_storage;
1499
16
  spl_handler_SplObjectStorage.read_dimension  = spl_object_storage_read_dimension;
1500
16
  spl_handler_SplObjectStorage.write_dimension = spl_object_storage_write_dimension;
1501
16
  spl_handler_SplObjectStorage.has_dimension   = spl_object_storage_has_dimension;
1502
16
  spl_handler_SplObjectStorage.unset_dimension = spl_object_storage_unset_dimension;
1503
1504
16
  memcpy(&spl_handler_MultipleIterator, &spl_handler_SplObjectStorage, sizeof(zend_object_handlers));
1505
1506
16
  spl_handler_MultipleIterator.write_dimension = spl_multiple_iterator_write_dimension;
1507
1508
16
  spl_ce_MultipleIterator = register_class_MultipleIterator(zend_ce_iterator);
1509
16
  spl_ce_MultipleIterator->create_object = spl_SplObjectStorage_new;
1510
16
  spl_ce_MultipleIterator->default_object_handlers = &spl_handler_MultipleIterator;
1511
1512
16
  return SUCCESS;
1513
16
}
1514
/* }}} */