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