Coverage Report

Created: 2026-09-14 06:25

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