xref: /php-src/ext/spl/spl_observer.c (revision 144d2ee2)
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 "ext/standard/php_array.h" /* For PHP_COUNT_* constants */
24 #include "ext/standard/php_var.h"
25 #include "zend_smart_str.h"
26 #include "zend_interfaces.h"
27 #include "zend_exceptions.h"
28 
29 #include "php_spl.h" /* For php_spl_object_hash() */
30 #include "spl_observer.h"
31 #include "spl_observer_arginfo.h"
32 #include "spl_iterators.h"
33 #include "spl_exceptions.h"
34 #include "spl_functions.h" /* For spl_set_private_debug_info_property() */
35 
36 PHPAPI zend_class_entry     *spl_ce_SplObserver;
37 PHPAPI zend_class_entry     *spl_ce_SplSubject;
38 PHPAPI zend_class_entry     *spl_ce_SplObjectStorage;
39 PHPAPI zend_class_entry     *spl_ce_MultipleIterator;
40 
41 static zend_object_handlers spl_handler_SplObjectStorage;
42 
43 /* Bit flags for marking internal functionality overridden by SplObjectStorage subclasses. */
44 #define SOS_OVERRIDDEN_READ_DIMENSION  1
45 #define SOS_OVERRIDDEN_WRITE_DIMENSION 2
46 #define SOS_OVERRIDDEN_UNSET_DIMENSION 4
47 
48 typedef struct _spl_SplObjectStorage { /* {{{ */
49 	HashTable         storage;
50 	zend_long         index;
51 	HashPosition      pos;
52 	/* In SplObjectStorage, flags is a hidden implementation detail to optimize ArrayAccess handlers.
53 	 * In MultipleIterator on a different class hierarchy, flags is a user settable value controlling iteration behavior. */
54 	zend_long         flags;
55 	zend_function    *fptr_get_hash;
56 	zend_object       std;
57 } spl_SplObjectStorage; /* }}} */
58 
59 /* {{{ storage is an assoc array of [zend_object*]=>[zval *obj, zval *inf] */
60 typedef struct _spl_SplObjectStorageElement {
61 	zend_object *obj;
62 	zval inf;
63 } spl_SplObjectStorageElement; /* }}} */
64 
spl_object_storage_from_obj(zend_object * obj)65 static inline spl_SplObjectStorage *spl_object_storage_from_obj(zend_object *obj) /* {{{ */ {
66 	return (spl_SplObjectStorage*)((char*)(obj) - XtOffsetOf(spl_SplObjectStorage, std));
67 }
68 /* }}} */
69 
70 #define Z_SPLOBJSTORAGE_P(zv)  spl_object_storage_from_obj(Z_OBJ_P((zv)))
71 
spl_SplObjectStorage_free_storage(zend_object * object)72 static void spl_SplObjectStorage_free_storage(zend_object *object) /* {{{ */
73 {
74 	spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
75 
76 	zend_object_std_dtor(&intern->std);
77 
78 	zend_hash_destroy(&intern->storage);
79 } /* }}} */
80 
spl_object_storage_get_hash(zend_hash_key * key,spl_SplObjectStorage * intern,zend_object * obj)81 static zend_result spl_object_storage_get_hash(zend_hash_key *key, spl_SplObjectStorage *intern, zend_object *obj) {
82 	if (UNEXPECTED(intern->fptr_get_hash)) {
83 		zval param;
84 		zval rv;
85 		ZVAL_OBJ(&param, obj);
86 		zend_call_method_with_1_params(&intern->std, intern->std.ce, &intern->fptr_get_hash, "getHash", &rv, &param);
87 		if (UNEXPECTED(Z_ISUNDEF(rv))) {
88 			/* An exception has occurred */
89 			return FAILURE;
90 		} else {
91 			/* TODO PHP 9: Remove this as this will be enforced from the return type */
92 			if (UNEXPECTED(Z_TYPE(rv) != IS_STRING)) {
93 				zend_type_error("%s::getHash(): Return value must be of type string, %s returned",
94 					ZSTR_VAL(intern->std.ce->name), zend_zval_value_name(&rv));
95 				zval_ptr_dtor(&rv);
96 				return FAILURE;
97 			} else {
98 				key->key = Z_STR(rv);
99 				return SUCCESS;
100 			}
101 		}
102 	} else {
103 		key->key = NULL;
104 		key->h = obj->handle;
105 		return SUCCESS;
106 	}
107 }
108 
spl_object_storage_free_hash(spl_SplObjectStorage * intern,zend_hash_key * key)109 static void spl_object_storage_free_hash(spl_SplObjectStorage *intern, zend_hash_key *key) {
110 	if (key->key) {
111 		zend_string_release_ex(key->key, 0);
112 	}
113 }
114 
spl_object_storage_dtor(zval * element)115 static void spl_object_storage_dtor(zval *element) /* {{{ */
116 {
117 	spl_SplObjectStorageElement *el = Z_PTR_P(element);
118 	if (el) {
119 		zend_object_release(el->obj);
120 		zval_ptr_dtor(&el->inf);
121 		efree(el);
122 	}
123 } /* }}} */
124 
spl_object_storage_get(spl_SplObjectStorage * intern,zend_hash_key * key)125 static spl_SplObjectStorageElement* spl_object_storage_get(spl_SplObjectStorage *intern, zend_hash_key *key) /* {{{ */
126 {
127 	if (key->key) {
128 		return zend_hash_find_ptr(&intern->storage, key->key);
129 	} else {
130 		return zend_hash_index_find_ptr(&intern->storage, key->h);
131 	}
132 } /* }}} */
133 
spl_object_storage_create_element(zend_object * obj,zval * inf)134 static spl_SplObjectStorageElement *spl_object_storage_create_element(zend_object *obj, zval *inf) /* {{{ */
135 {
136 	spl_SplObjectStorageElement *pelement = emalloc(sizeof(spl_SplObjectStorageElement));
137 	pelement->obj = obj;
138 	GC_ADDREF(obj);
139 	if (inf) {
140 		ZVAL_COPY(&pelement->inf, inf);
141 	} else {
142 		ZVAL_NULL(&pelement->inf);
143 	}
144 	return pelement;
145 } /* }}} */
146 
147 /* 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)148 static spl_SplObjectStorageElement *spl_object_storage_attach_handle(spl_SplObjectStorage *intern, zend_object *obj, zval *inf) /* {{{ */
149 {
150 	uint32_t handle = obj->handle;
151 	zval *entry_zv = zend_hash_index_lookup(&intern->storage, handle);
152 	spl_SplObjectStorageElement *pelement;
153 	ZEND_ASSERT(!(intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION));
154 
155 	if (Z_TYPE_P(entry_zv) != IS_NULL) {
156 		zval zv_inf;
157 		ZEND_ASSERT(Z_TYPE_P(entry_zv) == IS_PTR);
158 		pelement = Z_PTR_P(entry_zv);
159 		ZVAL_COPY_VALUE(&zv_inf, &pelement->inf);
160 		if (inf) {
161 			ZVAL_COPY(&pelement->inf, inf);
162 		} else {
163 			ZVAL_NULL(&pelement->inf);
164 		}
165 		/* Call the old value's destructor last, in case it moves the entry */
166 		zval_ptr_dtor(&zv_inf);
167 		return pelement;
168 	}
169 
170 	/* NULL initialization necessary because `spl_object_storage_create_element` could bail out due to OOM. */
171 	ZVAL_PTR(entry_zv, NULL);
172 	pelement = spl_object_storage_create_element(obj, inf);
173 	Z_PTR_P(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 	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 	spl_set_private_debug_info_property(spl_ce_SplObjectStorage, "storage", strlen("storage"), debug_info, &storage);
350 
351 	return debug_info;
352 }
353 /* }}} */
354 
355 /* overridden for garbage collection */
spl_object_storage_get_gc(zend_object * obj,zval ** table,int * n)356 static HashTable *spl_object_storage_get_gc(zend_object *obj, zval **table, int *n) /* {{{ */
357 {
358 	spl_SplObjectStorage *intern = spl_object_storage_from_obj(obj);
359 	spl_SplObjectStorageElement *element;
360 	zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
361 
362 	ZEND_HASH_FOREACH_PTR(&intern->storage, element) {
363 		zend_get_gc_buffer_add_obj(gc_buffer, element->obj);
364 		zend_get_gc_buffer_add_zval(gc_buffer, &element->inf);
365 	} ZEND_HASH_FOREACH_END();
366 
367 	zend_get_gc_buffer_use(gc_buffer, table, n);
368 	return zend_std_get_properties(obj);
369 }
370 /* }}} */
371 
spl_object_storage_compare_info(zval * e1,zval * e2)372 static int spl_object_storage_compare_info(zval *e1, zval *e2) /* {{{ */
373 {
374 	spl_SplObjectStorageElement *s1 = (spl_SplObjectStorageElement*)Z_PTR_P(e1);
375 	spl_SplObjectStorageElement *s2 = (spl_SplObjectStorageElement*)Z_PTR_P(e2);
376 
377 	return zend_compare(&s1->inf, &s2->inf);
378 }
379 /* }}} */
380 
spl_object_storage_compare_objects(zval * o1,zval * o2)381 static int spl_object_storage_compare_objects(zval *o1, zval *o2) /* {{{ */
382 {
383 	zend_object *zo1;
384 	zend_object *zo2;
385 
386 	ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2);
387 
388 	zo1 = (zend_object *)Z_OBJ_P(o1);
389 	zo2 = (zend_object *)Z_OBJ_P(o2);
390 
391 	if (zo1->ce != spl_ce_SplObjectStorage || zo2->ce != spl_ce_SplObjectStorage) {
392 		return ZEND_UNCOMPARABLE;
393 	}
394 
395 	return zend_hash_compare(&(Z_SPLOBJSTORAGE_P(o1))->storage, &(Z_SPLOBJSTORAGE_P(o2))->storage, (compare_func_t)spl_object_storage_compare_info, 0);
396 }
397 /* }}} */
398 
399 /* {{{ spl_array_object_new */
spl_SplObjectStorage_new(zend_class_entry * class_type)400 static zend_object *spl_SplObjectStorage_new(zend_class_entry *class_type)
401 {
402 	return spl_object_storage_new_ex(class_type, NULL);
403 }
404 /* }}} */
405 
406 /* 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)407 static bool spl_object_storage_contains(spl_SplObjectStorage *intern, zend_object *obj) /* {{{ */
408 {
409 	if (EXPECTED(!intern->fptr_get_hash)) {
410 		return zend_hash_index_find(&intern->storage, obj->handle) != NULL;
411 	}
412 	zend_hash_key key;
413 	if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) {
414 		return true;
415 	}
416 
417 	ZEND_ASSERT(key.key);
418 	bool found = zend_hash_exists(&intern->storage, key.key);
419 	zend_string_release_ex(key.key, 0);
420 
421 	return found;
422 } /* }}} */
423 
424 /* {{{ Attaches an object to the storage if not yet contained */
PHP_METHOD(SplObjectStorage,attach)425 PHP_METHOD(SplObjectStorage, attach)
426 {
427 	zend_object *obj;
428 	zval *inf = NULL;
429 
430 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
431 
432 	ZEND_PARSE_PARAMETERS_START(1, 2)
433 		Z_PARAM_OBJ(obj)
434 		Z_PARAM_OPTIONAL
435 		Z_PARAM_ZVAL(inf)
436 	ZEND_PARSE_PARAMETERS_END();
437 	spl_object_storage_attach(intern, obj, inf);
438 } /* }}} */
439 
440 // todo: make spl_object_storage_has_dimension return bool as well
spl_object_storage_has_dimension(zend_object * object,zval * offset,int check_empty)441 static int spl_object_storage_has_dimension(zend_object *object, zval *offset, int check_empty)
442 {
443 	spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
444 	if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) {
445 		/* Can't optimize empty()/isset() check if getHash, offsetExists, or offsetGet is overridden */
446 		return zend_std_has_dimension(object, offset, check_empty);
447 	}
448 	spl_SplObjectStorageElement *element = zend_hash_index_find_ptr(&intern->storage, Z_OBJ_HANDLE_P(offset));
449 	if (!element) {
450 		return 0;
451 	}
452 
453 	if (check_empty) {
454 		return i_zend_is_true(&element->inf);
455 	}
456 	/* NOTE: SplObjectStorage->offsetExists() is an alias of SplObjectStorage->contains(), so this returns true even if the value is null. */
457 	return 1;
458 }
459 
spl_object_storage_read_dimension(zend_object * object,zval * offset,int type,zval * rv)460 static zval *spl_object_storage_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
461 {
462 	spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
463 	if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) {
464 		/* Can't optimize it if getHash, offsetExists, or offsetGet is overridden */
465 		return zend_std_read_dimension(object, offset, type, rv);
466 	}
467 	spl_SplObjectStorageElement *element = zend_hash_index_find_ptr(&intern->storage, Z_OBJ_HANDLE_P(offset));
468 
469 	if (!element) {
470 		if (type == BP_VAR_IS) {
471 			return &EG(uninitialized_zval);
472 		}
473 		zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Object not found");
474 		return NULL;
475 	} else {
476 		/* 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.
477 		 * i.e. This prevents taking a reference to an entry of SplObjectStorage because offsetGet would return a non-reference. */
478 		ZVAL_COPY_DEREF(rv, &element->inf);
479 		return rv;
480 	}
481 }
482 
spl_object_storage_write_dimension(zend_object * object,zval * offset,zval * inf)483 static void spl_object_storage_write_dimension(zend_object *object, zval *offset, zval *inf)
484 {
485 	spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
486 	if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION))) {
487 		zend_std_write_dimension(object, offset, inf);
488 		return;
489 	}
490 	spl_object_storage_attach_handle(intern, Z_OBJ_P(offset), inf);
491 }
492 
spl_object_storage_unset_dimension(zend_object * object,zval * offset)493 static void spl_object_storage_unset_dimension(zend_object *object, zval *offset)
494 {
495 	spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
496 	if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_UNSET_DIMENSION))) {
497 		zend_std_unset_dimension(object, offset);
498 		return;
499 	}
500 	zend_hash_index_del(&intern->storage, Z_OBJ_HANDLE_P(offset));
501 }
502 
503 /* {{{ Detaches an object from the storage */
PHP_METHOD(SplObjectStorage,detach)504 PHP_METHOD(SplObjectStorage, detach)
505 {
506 	zend_object *obj;
507 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
508 
509 	ZEND_PARSE_PARAMETERS_START(1, 1)
510 		Z_PARAM_OBJ(obj)
511 	ZEND_PARSE_PARAMETERS_END();
512 	spl_object_storage_detach(intern, obj);
513 
514 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
515 	intern->index = 0;
516 } /* }}} */
517 
518 /* {{{ Returns the hash of an object */
PHP_METHOD(SplObjectStorage,getHash)519 PHP_METHOD(SplObjectStorage, getHash)
520 {
521 	zend_object *obj;
522 
523 	ZEND_PARSE_PARAMETERS_START(1, 1)
524 		Z_PARAM_OBJ(obj)
525 	ZEND_PARSE_PARAMETERS_END();
526 
527 	RETURN_NEW_STR(php_spl_object_hash(obj));
528 
529 } /* }}} */
530 
531 /* {{{ Returns associated information for a stored object */
PHP_METHOD(SplObjectStorage,offsetGet)532 PHP_METHOD(SplObjectStorage, offsetGet)
533 {
534 	zend_object *obj;
535 	spl_SplObjectStorageElement *element;
536 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
537 	zend_hash_key key;
538 
539 	ZEND_PARSE_PARAMETERS_START(1, 1)
540 		Z_PARAM_OBJ(obj)
541 	ZEND_PARSE_PARAMETERS_END();
542 
543 	if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) {
544 		RETURN_NULL();
545 	}
546 
547 	element = spl_object_storage_get(intern, &key);
548 	spl_object_storage_free_hash(intern, &key);
549 
550 	if (!element) {
551 		zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Object not found");
552 	} else {
553 		RETURN_COPY_DEREF(&element->inf);
554 	}
555 } /* }}} */
556 
557 /* {{{ Add all elements contained in $os */
PHP_METHOD(SplObjectStorage,addAll)558 PHP_METHOD(SplObjectStorage, addAll)
559 {
560 	zval *obj;
561 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
562 	spl_SplObjectStorage *other;
563 
564 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) {
565 		RETURN_THROWS();
566 	}
567 
568 	other = Z_SPLOBJSTORAGE_P(obj);
569 
570 	spl_object_storage_addall(intern, other);
571 
572 	RETURN_LONG(zend_hash_num_elements(&intern->storage));
573 } /* }}} */
574 
575 /* {{{ Remove all elements contained in $os */
PHP_METHOD(SplObjectStorage,removeAll)576 PHP_METHOD(SplObjectStorage, removeAll)
577 {
578 	zval *obj;
579 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
580 	spl_SplObjectStorage *other;
581 	spl_SplObjectStorageElement *element;
582 
583 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) {
584 		RETURN_THROWS();
585 	}
586 
587 	other = Z_SPLOBJSTORAGE_P(obj);
588 
589 	zend_hash_internal_pointer_reset(&other->storage);
590 	while ((element = zend_hash_get_current_data_ptr(&other->storage)) != NULL) {
591 		if (spl_object_storage_detach(intern, element->obj) == FAILURE) {
592 			zend_hash_move_forward(&other->storage);
593 		}
594 	}
595 
596 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
597 	intern->index = 0;
598 
599 	RETURN_LONG(zend_hash_num_elements(&intern->storage));
600 } /* }}} */
601 
602 /* {{{ Remove elements not common to both this SplObjectStorage instance and $os */
PHP_METHOD(SplObjectStorage,removeAllExcept)603 PHP_METHOD(SplObjectStorage, removeAllExcept)
604 {
605 	zval *obj;
606 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
607 	spl_SplObjectStorage *other;
608 	spl_SplObjectStorageElement *element;
609 
610 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) {
611 		RETURN_THROWS();
612 	}
613 
614 	other = Z_SPLOBJSTORAGE_P(obj);
615 
616 	ZEND_HASH_FOREACH_PTR(&intern->storage, element) {
617 		if (!spl_object_storage_contains(other, element->obj)) {
618 			spl_object_storage_detach(intern, element->obj);
619 		}
620 	} ZEND_HASH_FOREACH_END();
621 
622 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
623 	intern->index = 0;
624 
625 	RETURN_LONG(zend_hash_num_elements(&intern->storage));
626 }
627 /* }}} */
628 
629 /* {{{ Determine whether an object is contained in the storage */
PHP_METHOD(SplObjectStorage,contains)630 PHP_METHOD(SplObjectStorage, contains)
631 {
632 	zend_object *obj;
633 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
634 
635 	ZEND_PARSE_PARAMETERS_START(1, 1)
636 		Z_PARAM_OBJ(obj)
637 	ZEND_PARSE_PARAMETERS_END();
638 	RETURN_BOOL(spl_object_storage_contains(intern, obj));
639 } /* }}} */
640 
641 /* {{{ Determine number of objects in storage */
PHP_METHOD(SplObjectStorage,count)642 PHP_METHOD(SplObjectStorage, count)
643 {
644 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
645 	zend_long mode = PHP_COUNT_NORMAL;
646 
647 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &mode) == FAILURE) {
648 		RETURN_THROWS();
649 	}
650 
651 	if (mode == PHP_COUNT_RECURSIVE) {
652 		RETURN_LONG(php_count_recursive(&intern->storage));
653 	}
654 
655 	RETURN_LONG(zend_hash_num_elements(&intern->storage));
656 } /* }}} */
657 
658 /* {{{ Rewind to first position */
PHP_METHOD(SplObjectStorage,rewind)659 PHP_METHOD(SplObjectStorage, rewind)
660 {
661 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
662 
663 	if (zend_parse_parameters_none() == FAILURE) {
664 		RETURN_THROWS();
665 	}
666 
667 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
668 	intern->index = 0;
669 } /* }}} */
670 
671 /* {{{ Returns whether current position is valid */
PHP_METHOD(SplObjectStorage,valid)672 PHP_METHOD(SplObjectStorage, valid)
673 {
674 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
675 
676 	if (zend_parse_parameters_none() == FAILURE) {
677 		RETURN_THROWS();
678 	}
679 
680 	RETURN_BOOL(zend_hash_has_more_elements_ex(&intern->storage, &intern->pos) == SUCCESS);
681 } /* }}} */
682 
683 /* {{{ Returns current key */
PHP_METHOD(SplObjectStorage,key)684 PHP_METHOD(SplObjectStorage, key)
685 {
686 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
687 
688 	if (zend_parse_parameters_none() == FAILURE) {
689 		RETURN_THROWS();
690 	}
691 
692 	RETURN_LONG(intern->index);
693 } /* }}} */
694 
695 /* {{{ Returns current element */
PHP_METHOD(SplObjectStorage,current)696 PHP_METHOD(SplObjectStorage, current)
697 {
698 	spl_SplObjectStorageElement *element;
699 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
700 
701 	if (zend_parse_parameters_none() == FAILURE) {
702 		RETURN_THROWS();
703 	}
704 
705 	if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) == NULL) {
706 		zend_throw_exception(spl_ce_RuntimeException, "Called current() on invalid iterator", 0);
707 		RETURN_THROWS();
708 	}
709 	ZVAL_OBJ_COPY(return_value, element->obj);
710 } /* }}} */
711 
712 /* {{{ Returns associated information to current element */
PHP_METHOD(SplObjectStorage,getInfo)713 PHP_METHOD(SplObjectStorage, getInfo)
714 {
715 	spl_SplObjectStorageElement *element;
716 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
717 
718 	if (zend_parse_parameters_none() == FAILURE) {
719 		RETURN_THROWS();
720 	}
721 
722 	if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) == NULL) {
723 		RETURN_NULL();
724 	}
725 	ZVAL_COPY(return_value, &element->inf);
726 } /* }}} */
727 
728 /* {{{ Sets associated information of current element to $inf */
PHP_METHOD(SplObjectStorage,setInfo)729 PHP_METHOD(SplObjectStorage, setInfo)
730 {
731 	spl_SplObjectStorageElement *element;
732 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
733 	zval *inf;
734 
735 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &inf) == FAILURE) {
736 		RETURN_THROWS();
737 	}
738 
739 	if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) == NULL) {
740 		RETURN_NULL();
741 	}
742 	zval garbage;
743 	ZVAL_COPY_VALUE(&garbage, &element->inf);
744 	ZVAL_COPY(&element->inf, inf);
745 	zval_ptr_dtor(&garbage);
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 /* {{{ Seek to position. */
PHP_METHOD(SplObjectStorage,seek)762 PHP_METHOD(SplObjectStorage, seek)
763 {
764 	zend_long position;
765 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
766 
767 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &position) == FAILURE) {
768 		RETURN_THROWS();
769 	}
770 
771 	if (position < 0 || position >= zend_hash_num_elements(&intern->storage)) {
772 		zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Seek position " ZEND_LONG_FMT " is out of range", position);
773 		RETURN_THROWS();
774 	}
775 
776 	if (position == 0) {
777 		/* fast path */
778 		zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
779 		intern->index = 0;
780 	} else if (position > intern->index) {
781 		/* unlike the optimization below, it's not cheap to go to the end */
782 		do {
783 			zend_hash_move_forward_ex(&intern->storage, &intern->pos);
784 			intern->index++;
785 		} while (position > intern->index);
786 	} else if (position < intern->index) {
787 		/* optimization: check if it's more profitable to reset and do a forwards seek instead, it's cheap to reset */
788 		if (intern->index - position > position) {
789 			zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
790 			intern->index = 0;
791 			do {
792 				zend_hash_move_forward_ex(&intern->storage, &intern->pos);
793 				intern->index++;
794 			} while (position > intern->index);
795 		} else {
796 			do {
797 				zend_hash_move_backwards_ex(&intern->storage, &intern->pos);
798 				intern->index--;
799 			} while (position < intern->index);
800 		}
801 	}
802 } /* }}} */
803 
804 /* {{{ Serializes storage */
PHP_METHOD(SplObjectStorage,serialize)805 PHP_METHOD(SplObjectStorage, serialize)
806 {
807 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
808 
809 	spl_SplObjectStorageElement *element;
810 	zval members, flags;
811 	HashPosition      pos;
812 	php_serialize_data_t var_hash;
813 	smart_str buf = {0};
814 
815 	if (zend_parse_parameters_none() == FAILURE) {
816 		RETURN_THROWS();
817 	}
818 
819 	PHP_VAR_SERIALIZE_INIT(var_hash);
820 
821 	/* storage */
822 	smart_str_appendl(&buf, "x:", 2);
823 	ZVAL_LONG(&flags, zend_hash_num_elements(&intern->storage));
824 	php_var_serialize(&buf, &flags, &var_hash);
825 
826 	zend_hash_internal_pointer_reset_ex(&intern->storage, &pos);
827 
828 	while (zend_hash_has_more_elements_ex(&intern->storage, &pos) == SUCCESS) {
829 		zval obj;
830 		if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &pos)) == NULL) {
831 			smart_str_free(&buf);
832 			PHP_VAR_SERIALIZE_DESTROY(var_hash);
833 			RETURN_NULL();
834 		}
835 		ZVAL_OBJ(&obj, element->obj);
836 
837 		/* Protect against modification; we need a full copy because the data may be refcounted. */
838 		zval inf_copy;
839 		ZVAL_COPY(&inf_copy, &element->inf);
840 
841 		php_var_serialize(&buf, &obj, &var_hash);
842 		smart_str_appendc(&buf, ',');
843 		php_var_serialize(&buf, &inf_copy, &var_hash);
844 		smart_str_appendc(&buf, ';');
845 		zend_hash_move_forward_ex(&intern->storage, &pos);
846 
847 		zval_ptr_dtor(&inf_copy);
848 	}
849 
850 	/* members */
851 	smart_str_appendl(&buf, "m:", 2);
852 
853 	ZVAL_ARR(&members, zend_array_dup(zend_std_get_properties(Z_OBJ_P(ZEND_THIS))));
854 	php_var_serialize(&buf, &members, &var_hash); /* finishes the string */
855 	zval_ptr_dtor(&members);
856 
857 	/* done */
858 	PHP_VAR_SERIALIZE_DESTROY(var_hash);
859 
860 	RETURN_STR(smart_str_extract(&buf));
861 } /* }}} */
862 
863 /* {{{ Unserializes storage */
PHP_METHOD(SplObjectStorage,unserialize)864 PHP_METHOD(SplObjectStorage, unserialize)
865 {
866 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
867 
868 	char *buf;
869 	size_t buf_len;
870 	const unsigned char *p, *s;
871 	php_unserialize_data_t var_hash;
872 	zval *pcount, *pmembers;
873 	spl_SplObjectStorageElement *element;
874 	zend_long count;
875 
876 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &buf, &buf_len) == FAILURE) {
877 		RETURN_THROWS();
878 	}
879 
880 	if (buf_len == 0) {
881 		return;
882 	}
883 
884 	/* storage */
885 	s = p = (const unsigned char*)buf;
886 	PHP_VAR_UNSERIALIZE_INIT(var_hash);
887 
888 	if (*p!= 'x' || *++p != ':') {
889 		goto outexcept;
890 	}
891 	++p;
892 
893 	pcount = var_tmp_var(&var_hash);
894 	if (!php_var_unserialize(pcount, &p, s + buf_len, &var_hash) || Z_TYPE_P(pcount) != IS_LONG) {
895 		goto outexcept;
896 	}
897 
898 	--p; /* for ';' */
899 	count = Z_LVAL_P(pcount);
900 	if (count < 0) {
901 		goto outexcept;
902 	}
903 
904 	while (count-- > 0) {
905 		spl_SplObjectStorageElement *pelement;
906 		zend_hash_key key;
907 		zval *entry = var_tmp_var(&var_hash);
908 		zval inf;
909 		ZVAL_UNDEF(&inf);
910 
911 		if (*p != ';') {
912 			goto outexcept;
913 		}
914 		++p;
915 		if(*p != 'O' && *p != 'C' && *p != 'r') {
916 			goto outexcept;
917 		}
918 		/* store reference to allow cross-references between different elements */
919 		if (!php_var_unserialize(entry, &p, s + buf_len, &var_hash)) {
920 			goto outexcept;
921 		}
922 		if (*p == ',') { /* new version has inf */
923 			++p;
924 			if (!php_var_unserialize(&inf, &p, s + buf_len, &var_hash)) {
925 				zval_ptr_dtor(&inf);
926 				goto outexcept;
927 			}
928 		}
929 		if (Z_TYPE_P(entry) != IS_OBJECT) {
930 			zval_ptr_dtor(&inf);
931 			goto outexcept;
932 		}
933 
934 		if (spl_object_storage_get_hash(&key, intern, Z_OBJ_P(entry)) == FAILURE) {
935 			zval_ptr_dtor(&inf);
936 			goto outexcept;
937 		}
938 		pelement = spl_object_storage_get(intern, &key);
939 		spl_object_storage_free_hash(intern, &key);
940 		if (pelement) {
941 			zval obj;
942 			if (!Z_ISUNDEF(pelement->inf)) {
943 				var_push_dtor(&var_hash, &pelement->inf);
944 			}
945 			ZVAL_OBJ(&obj, pelement->obj);
946 			var_push_dtor(&var_hash, &obj);
947 		}
948 		element = spl_object_storage_attach(intern, Z_OBJ_P(entry), Z_ISUNDEF(inf)?NULL:&inf);
949 		var_replace(&var_hash, &inf, &element->inf);
950 		zval_ptr_dtor(&inf);
951 	}
952 
953 	if (*p != ';') {
954 		goto outexcept;
955 	}
956 	++p;
957 
958 	/* members */
959 	if (*p!= 'm' || *++p != ':') {
960 		goto outexcept;
961 	}
962 	++p;
963 
964 	pmembers = var_tmp_var(&var_hash);
965 	if (!php_var_unserialize(pmembers, &p, s + buf_len, &var_hash) || Z_TYPE_P(pmembers) != IS_ARRAY) {
966 		goto outexcept;
967 	}
968 
969 	/* copy members */
970 	object_properties_load(&intern->std, Z_ARRVAL_P(pmembers));
971 
972 	PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
973 	return;
974 
975 outexcept:
976 	PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
977 	zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Error at offset %zd of %zd bytes", ((char*)p - buf), buf_len);
978 	RETURN_THROWS();
979 
980 } /* }}} */
981 
982 /* {{{ */
PHP_METHOD(SplObjectStorage,__serialize)983 PHP_METHOD(SplObjectStorage, __serialize)
984 {
985 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
986 	spl_SplObjectStorageElement *elem;
987 	zval tmp;
988 
989 	if (zend_parse_parameters_none() == FAILURE) {
990 		RETURN_THROWS();
991 	}
992 
993 	array_init(return_value);
994 
995 	/* storage */
996 	array_init_size(&tmp, 2 * zend_hash_num_elements(&intern->storage));
997 	ZEND_HASH_FOREACH_PTR(&intern->storage, elem) {
998 		zval obj;
999 		ZVAL_OBJ_COPY(&obj, elem->obj);
1000 		zend_hash_next_index_insert(Z_ARRVAL(tmp), &obj);
1001 		Z_TRY_ADDREF(elem->inf);
1002 		zend_hash_next_index_insert(Z_ARRVAL(tmp), &elem->inf);
1003 	} ZEND_HASH_FOREACH_END();
1004 	zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1005 
1006 	/* members */
1007 	ZVAL_ARR(&tmp, zend_proptable_to_symtable(
1008 		zend_std_get_properties(&intern->std), /* always_duplicate */ 1));
1009 	zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1010 } /* }}} */
1011 
1012 /* {{{ */
PHP_METHOD(SplObjectStorage,__unserialize)1013 PHP_METHOD(SplObjectStorage, __unserialize)
1014 {
1015 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1016 	HashTable *data;
1017 	zval *storage_zv, *members_zv, *key, *val;
1018 
1019 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
1020 		RETURN_THROWS();
1021 	}
1022 
1023 	storage_zv = zend_hash_index_find(data, 0);
1024 	members_zv = zend_hash_index_find(data, 1);
1025 	if (!storage_zv || !members_zv ||
1026 			Z_TYPE_P(storage_zv) != IS_ARRAY || Z_TYPE_P(members_zv) != IS_ARRAY) {
1027 		zend_throw_exception(spl_ce_UnexpectedValueException,
1028 			"Incomplete or ill-typed serialization data", 0);
1029 		RETURN_THROWS();
1030 	}
1031 
1032 	if (zend_hash_num_elements(Z_ARRVAL_P(storage_zv)) % 2 != 0) {
1033 		zend_throw_exception(spl_ce_UnexpectedValueException, "Odd number of elements", 0);
1034 		RETURN_THROWS();
1035 	}
1036 
1037 	key = NULL;
1038 	ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(storage_zv), val) {
1039 		if (key) {
1040 			if (Z_TYPE_P(key) != IS_OBJECT) {
1041 				zend_throw_exception(spl_ce_UnexpectedValueException, "Non-object key", 0);
1042 				RETURN_THROWS();
1043 			}
1044 
1045 			ZVAL_DEREF(val);
1046 			spl_object_storage_attach(intern, Z_OBJ_P(key), val);
1047 			key = NULL;
1048 		} else {
1049 			key = val;
1050 		}
1051 	} ZEND_HASH_FOREACH_END();
1052 
1053 	object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
1054 }
1055 
1056 /* {{{ */
PHP_METHOD(SplObjectStorage,__debugInfo)1057 PHP_METHOD(SplObjectStorage, __debugInfo)
1058 {
1059 	if (zend_parse_parameters_none() == FAILURE) {
1060 		RETURN_THROWS();
1061 	}
1062 
1063 	RETURN_ARR(spl_object_storage_debug_info(Z_OBJ_P(ZEND_THIS)));
1064 }
1065 /* }}} */
1066 
1067 #define SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT   1
1068 #define SPL_MULTIPLE_ITERATOR_GET_ALL_KEY       2
1069 
1070 /* {{{ Iterator that iterates over several iterators one after the other */
PHP_METHOD(MultipleIterator,__construct)1071 PHP_METHOD(MultipleIterator, __construct)
1072 {
1073 	spl_SplObjectStorage   *intern;
1074 	zend_long               flags = MIT_NEED_ALL|MIT_KEYS_NUMERIC;
1075 
1076 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &flags) == FAILURE) {
1077 		RETURN_THROWS();
1078 	}
1079 
1080 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1081 	intern->flags = flags;
1082 }
1083 /* }}} */
1084 
1085 /* {{{ Return current flags */
PHP_METHOD(MultipleIterator,getFlags)1086 PHP_METHOD(MultipleIterator, getFlags)
1087 {
1088 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1089 
1090 	if (zend_parse_parameters_none() == FAILURE) {
1091 		RETURN_THROWS();
1092 	}
1093 	RETURN_LONG(intern->flags);
1094 }
1095 /* }}} */
1096 
1097 /* {{{ Set flags */
PHP_METHOD(MultipleIterator,setFlags)1098 PHP_METHOD(MultipleIterator, setFlags)
1099 {
1100 	spl_SplObjectStorage *intern;
1101 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1102 
1103 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &intern->flags) == FAILURE) {
1104 		RETURN_THROWS();
1105 	}
1106 }
1107 /* }}} */
1108 
1109 /* {{{ Attach a new iterator */
PHP_METHOD(MultipleIterator,attachIterator)1110 PHP_METHOD(MultipleIterator, attachIterator)
1111 {
1112 	spl_SplObjectStorage *intern;
1113 	zend_object *iterator = NULL;
1114 	zval zinfo;
1115 	zend_string *info_str;
1116 	zend_long info_long;
1117 	bool info_is_null = 1;
1118 
1119 	ZEND_PARSE_PARAMETERS_START(1, 2)
1120 		Z_PARAM_OBJ_OF_CLASS(iterator, zend_ce_iterator)
1121 		Z_PARAM_OPTIONAL
1122 		Z_PARAM_STR_OR_LONG_OR_NULL(info_str, info_long, info_is_null)
1123 	ZEND_PARSE_PARAMETERS_END();
1124 
1125 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1126 
1127 	if (!info_is_null) {
1128 		spl_SplObjectStorageElement *element;
1129 
1130 		if (info_str) {
1131 			ZVAL_STR(&zinfo, info_str);
1132 		} else {
1133 			ZVAL_LONG(&zinfo, info_long);
1134 		}
1135 
1136 		zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1137 		while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL) {
1138 			if (fast_is_identical_function(&zinfo, &element->inf)) {
1139 				zend_throw_exception(spl_ce_InvalidArgumentException, "Key duplication error", 0);
1140 				RETURN_THROWS();
1141 			}
1142 			zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1143 		}
1144 
1145 		spl_object_storage_attach(intern, iterator, &zinfo);
1146 	} else {
1147 		spl_object_storage_attach(intern, iterator, NULL);
1148 	}
1149 }
1150 /* }}} */
1151 
1152 /* {{{ Detaches an iterator */
PHP_METHOD(MultipleIterator,detachIterator)1153 PHP_METHOD(MultipleIterator, detachIterator)
1154 {
1155 	zval *iterator;
1156 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1157 
1158 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &iterator, zend_ce_iterator) == FAILURE) {
1159 		RETURN_THROWS();
1160 	}
1161 	spl_object_storage_detach(intern, Z_OBJ_P(iterator));
1162 
1163 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1164 	intern->index = 0;
1165 } /* }}} */
1166 
1167 /* {{{ Determine whether the iterator exists */
PHP_METHOD(MultipleIterator,containsIterator)1168 PHP_METHOD(MultipleIterator, containsIterator)
1169 {
1170 	zval *iterator;
1171 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1172 
1173 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &iterator, zend_ce_iterator) == FAILURE) {
1174 		RETURN_THROWS();
1175 	}
1176 	RETURN_BOOL(spl_object_storage_contains(intern, Z_OBJ_P(iterator)));
1177 } /* }}} */
1178 
PHP_METHOD(MultipleIterator,countIterators)1179 PHP_METHOD(MultipleIterator, countIterators)
1180 {
1181 	spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1182 
1183 	if (zend_parse_parameters_none() == FAILURE) {
1184 		RETURN_THROWS();
1185 	}
1186 
1187 	RETURN_LONG(zend_hash_num_elements(&intern->storage));
1188 }
1189 
1190 /* {{{ Rewind all attached iterator instances */
PHP_METHOD(MultipleIterator,rewind)1191 PHP_METHOD(MultipleIterator, rewind)
1192 {
1193 	spl_SplObjectStorage        *intern;
1194 	spl_SplObjectStorageElement *element;
1195 
1196 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1197 
1198 	if (zend_parse_parameters_none() == FAILURE) {
1199 		RETURN_THROWS();
1200 	}
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_rewind, it, NULL);
1206 		zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1207 	}
1208 }
1209 /* }}} */
1210 
1211 /* {{{ Move all attached iterator instances forward */
PHP_METHOD(MultipleIterator,next)1212 PHP_METHOD(MultipleIterator, next)
1213 {
1214 	spl_SplObjectStorage        *intern;
1215 	spl_SplObjectStorageElement *element;
1216 
1217 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1218 
1219 	if (zend_parse_parameters_none() == FAILURE) {
1220 		RETURN_THROWS();
1221 	}
1222 
1223 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1224 	while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL && !EG(exception)) {
1225 		zend_object *it = element->obj;
1226 		zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_next, it, NULL);
1227 		zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1228 	}
1229 }
1230 /* }}} */
1231 
1232 /* {{{ Return whether all or one sub iterator is valid depending on flags */
PHP_METHOD(MultipleIterator,valid)1233 PHP_METHOD(MultipleIterator, valid)
1234 {
1235 	spl_SplObjectStorage        *intern;
1236 	spl_SplObjectStorageElement *element;
1237 	zval                         retval;
1238 	zend_long                         expect, valid;
1239 
1240 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1241 
1242 	if (zend_parse_parameters_none() == FAILURE) {
1243 		RETURN_THROWS();
1244 	}
1245 
1246 	if (!zend_hash_num_elements(&intern->storage)) {
1247 		RETURN_FALSE;
1248 	}
1249 
1250 	expect = (intern->flags & MIT_NEED_ALL) ? 1 : 0;
1251 
1252 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1253 	while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL && !EG(exception)) {
1254 		zend_object *it = element->obj;
1255 		zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_valid, it, &retval);
1256 
1257 		if (!Z_ISUNDEF(retval)) {
1258 			valid = (Z_TYPE(retval) == IS_TRUE);
1259 			zval_ptr_dtor(&retval);
1260 		} else {
1261 			valid = 0;
1262 		}
1263 
1264 		if (expect != valid) {
1265 			RETURN_BOOL(!expect);
1266 		}
1267 
1268 		zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1269 	}
1270 
1271 	RETURN_BOOL(expect);
1272 }
1273 /* }}} */
1274 
spl_multiple_iterator_get_all(spl_SplObjectStorage * intern,int get_type,zval * return_value)1275 static void spl_multiple_iterator_get_all(spl_SplObjectStorage *intern, int get_type, zval *return_value) /* {{{ */
1276 {
1277 	spl_SplObjectStorageElement *element;
1278 	zval                         retval;
1279 	int                          valid = 1, num_elements;
1280 
1281 	num_elements = zend_hash_num_elements(&intern->storage);
1282 	if (num_elements < 1) {
1283 		zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Called %s() on an invalid iterator",
1284 			get_type == SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT ? "current" : "key");
1285 		RETURN_THROWS();
1286 	}
1287 
1288 	array_init_size(return_value, num_elements);
1289 
1290 	zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
1291 	while ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) != NULL && !EG(exception)) {
1292 		zend_object *it = element->obj;
1293 		zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_valid, it, &retval);
1294 
1295 		if (!Z_ISUNDEF(retval)) {
1296 			valid = Z_TYPE(retval) == IS_TRUE;
1297 			zval_ptr_dtor(&retval);
1298 		} else {
1299 			valid = 0;
1300 		}
1301 
1302 		if (valid) {
1303 			if (SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT == get_type) {
1304 				zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_current, it, &retval);
1305 			} else {
1306 				zend_call_known_instance_method_with_0_params(it->ce->iterator_funcs_ptr->zf_key, it, &retval);
1307 			}
1308 			if (Z_ISUNDEF(retval)) {
1309 				zend_throw_exception(spl_ce_RuntimeException, "Failed to call sub iterator method", 0);
1310 				return;
1311 			}
1312 		} else if (intern->flags & MIT_NEED_ALL) {
1313 			if (SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT == get_type) {
1314 				zend_throw_exception(spl_ce_RuntimeException, "Called current() with non valid sub iterator", 0);
1315 			} else {
1316 				zend_throw_exception(spl_ce_RuntimeException, "Called key() with non valid sub iterator", 0);
1317 			}
1318 			return;
1319 		} else {
1320 			ZVAL_NULL(&retval);
1321 		}
1322 
1323 		if (intern->flags & MIT_KEYS_ASSOC) {
1324 			switch (Z_TYPE(element->inf)) {
1325 				case IS_LONG:
1326 					add_index_zval(return_value, Z_LVAL(element->inf), &retval);
1327 					break;
1328 				case IS_STRING:
1329 					zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR(element->inf), &retval);
1330 					break;
1331 				default:
1332 					zval_ptr_dtor(&retval);
1333 					zend_throw_exception(spl_ce_InvalidArgumentException, "Sub-Iterator is associated with NULL", 0);
1334 					return;
1335 			}
1336 		} else {
1337 			add_next_index_zval(return_value, &retval);
1338 		}
1339 
1340 		zend_hash_move_forward_ex(&intern->storage, &intern->pos);
1341 	}
1342 }
1343 /* }}} */
1344 
1345 /* {{{ Return an array of all registered Iterator instances current() result */
PHP_METHOD(MultipleIterator,current)1346 PHP_METHOD(MultipleIterator, current)
1347 {
1348 	spl_SplObjectStorage        *intern;
1349 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1350 
1351 	if (zend_parse_parameters_none() == FAILURE) {
1352 		RETURN_THROWS();
1353 	}
1354 
1355 	spl_multiple_iterator_get_all(intern, SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT, return_value);
1356 }
1357 /* }}} */
1358 
1359 /* {{{ Return an array of all registered Iterator instances key() result */
PHP_METHOD(MultipleIterator,key)1360 PHP_METHOD(MultipleIterator, key)
1361 {
1362 	spl_SplObjectStorage *intern;
1363 	intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
1364 
1365 	if (zend_parse_parameters_none() == FAILURE) {
1366 		RETURN_THROWS();
1367 	}
1368 
1369 	spl_multiple_iterator_get_all(intern, SPL_MULTIPLE_ITERATOR_GET_ALL_KEY, return_value);
1370 }
1371 /* }}} */
1372 
1373 /* {{{ PHP_MINIT_FUNCTION(spl_observer) */
PHP_MINIT_FUNCTION(spl_observer)1374 PHP_MINIT_FUNCTION(spl_observer)
1375 {
1376 	spl_ce_SplObserver = register_class_SplObserver();
1377 	spl_ce_SplSubject = register_class_SplSubject();
1378 
1379 	spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, spl_ce_SeekableIterator, zend_ce_serializable, zend_ce_arrayaccess);
1380 	spl_ce_SplObjectStorage->create_object = spl_SplObjectStorage_new;
1381 	spl_ce_SplObjectStorage->default_object_handlers = &spl_handler_SplObjectStorage;
1382 
1383 	memcpy(&spl_handler_SplObjectStorage, &std_object_handlers, sizeof(zend_object_handlers));
1384 
1385 	spl_handler_SplObjectStorage.offset          = XtOffsetOf(spl_SplObjectStorage, std);
1386 	spl_handler_SplObjectStorage.compare         = spl_object_storage_compare_objects;
1387 	spl_handler_SplObjectStorage.clone_obj       = spl_object_storage_clone;
1388 	spl_handler_SplObjectStorage.get_gc          = spl_object_storage_get_gc;
1389 	spl_handler_SplObjectStorage.free_obj        = spl_SplObjectStorage_free_storage;
1390 	spl_handler_SplObjectStorage.read_dimension  = spl_object_storage_read_dimension;
1391 	spl_handler_SplObjectStorage.write_dimension = spl_object_storage_write_dimension;
1392 	spl_handler_SplObjectStorage.has_dimension   = spl_object_storage_has_dimension;
1393 	spl_handler_SplObjectStorage.unset_dimension = spl_object_storage_unset_dimension;
1394 
1395 	spl_ce_MultipleIterator = register_class_MultipleIterator(zend_ce_iterator);
1396 	spl_ce_MultipleIterator->create_object = spl_SplObjectStorage_new;
1397 	spl_ce_MultipleIterator->default_object_handlers = &spl_handler_SplObjectStorage;
1398 
1399 	return SUCCESS;
1400 }
1401 /* }}} */
1402