Coverage Report

Created: 2026-06-02 06:36

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