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