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