@@ -44,10 +44,17 @@ PHPAPI zend_class_entry     *spl_ce_MultipleIterator;
4444
4545PHPAPI  zend_object_handlers  spl_handler_SplObjectStorage ;
4646
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+ 
4752typedef  struct  _spl_SplObjectStorage  { /* {{{ */ 
4853	HashTable          storage ;
4954	zend_long          index ;
5055	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. */ 
5158	zend_long          flags ;
5259	zend_function     * fptr_get_hash ;
5360	zend_object        std ;
@@ -76,7 +83,7 @@ void spl_SplObjectStorage_free_storage(zend_object *object) /* {{{ */
7683} /* }}} */ 
7784
7885static  int  spl_object_storage_get_hash (zend_hash_key  * key , spl_SplObjectStorage  * intern , zend_object  * obj ) {
79- 	if  (intern -> fptr_get_hash ) {
86+ 	if  (UNEXPECTED ( intern -> fptr_get_hash ) ) {
8087		zval  param ;
8188		zval  rv ;
8289		ZVAL_OBJ (& param , obj );
@@ -125,8 +132,55 @@ static spl_SplObjectStorageElement* spl_object_storage_get(spl_SplObjectStorage
125132	}
126133} /* }}} */ 
127134
135+ static  spl_SplObjectStorageElement  * spl_object_storage_create_element (zend_object  * obj , zval  * inf ) /* {{{ */ 
136+ {
137+ 	spl_SplObjectStorageElement  * pelement  =  emalloc (sizeof (spl_SplObjectStorageElement ));
138+ 	pelement -> obj  =  obj ;
139+ 	GC_ADDREF (obj );
140+ 	if  (inf ) {
141+ 		ZVAL_COPY (& pelement -> inf , inf );
142+ 	} else  {
143+ 		ZVAL_NULL (& pelement -> inf );
144+ 	}
145+ 	return  pelement ;
146+ } /* }}} */ 
147+ 
148+ /* A faster version of spl_object_storage_attach used when neither SplObjectStorage->getHash nor SplObjectStorage->offsetSet is overridden. */ 
149+ static  spl_SplObjectStorageElement  * spl_object_storage_attach_handle (spl_SplObjectStorage  * intern , zend_object  * obj , zval  * inf ) /* {{{ */ 
150+ {
151+ 	uint32_t  handle  =  obj -> handle ;
152+ 	zval  * entry_zv  =  zend_hash_index_lookup (& intern -> storage , handle );
153+ 	spl_SplObjectStorageElement  * pelement ;
154+ 	ZEND_ASSERT (!(intern -> flags  &  SOS_OVERRIDDEN_WRITE_DIMENSION ));
155+ 
156+ 	if  (Z_TYPE_P (entry_zv ) !=  IS_NULL ) {
157+ 		zval  zv_inf ;
158+ 		ZEND_ASSERT (Z_TYPE_P (entry_zv ) ==  IS_PTR );
159+ 		pelement  =  Z_PTR_P (entry_zv );
160+ 		ZVAL_COPY_VALUE (& zv_inf , & pelement -> inf );
161+ 		ZEND_ASSERT (Z_TYPE (zv_inf ) !=  IS_REFERENCE );
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+ 
128177spl_SplObjectStorageElement  * spl_object_storage_attach (spl_SplObjectStorage  * intern , zend_object  * obj , zval  * inf ) /* {{{ */ 
129178{
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+ 
130184	spl_SplObjectStorageElement  * pelement , element ;
131185	zend_hash_key  key ;
132186	if  (spl_object_storage_get_hash (& key , intern , obj ) ==  FAILURE ) {
@@ -136,13 +190,17 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
136190	pelement  =  spl_object_storage_get (intern , & key );
137191
138192	if  (pelement ) {
139- 		zval_ptr_dtor (& pelement -> inf );
193+ 		zval  zv_inf ;
194+ 		ZVAL_COPY_VALUE (& zv_inf , & pelement -> inf );
195+ 		ZEND_ASSERT (Z_TYPE (zv_inf ) !=  IS_REFERENCE );
140196		if  (inf ) {
141197			ZVAL_COPY (& pelement -> inf , inf );
142198		} else  {
143199			ZVAL_NULL (& pelement -> inf );
144200		}
145201		spl_object_storage_free_hash (intern , & key );
202+ 		/* Call the old value's destructor last, in case it moves the entry */ 
203+ 		zval_ptr_dtor (& zv_inf );
146204		return  pelement ;
147205	}
148206
@@ -164,6 +222,9 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
164222
165223static  int  spl_object_storage_detach (spl_SplObjectStorage  * intern , zend_object  * obj ) /* {{{ */ 
166224{
225+ 	if  (EXPECTED (!(intern -> flags  &  SOS_OVERRIDDEN_UNSET_DIMENSION ))) {
226+ 		return  zend_hash_index_del (& intern -> storage , obj -> handle );
227+ 	}
167228	int  ret  =  FAILURE ;
168229	zend_hash_key  key ;
169230	if  (spl_object_storage_get_hash (& key , intern , obj ) ==  FAILURE ) {
@@ -189,6 +250,9 @@ void spl_object_storage_addall(spl_SplObjectStorage *intern, spl_SplObjectStorag
189250	intern -> index  =  0 ;
190251} /* }}} */ 
191252
253+ #define  SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , zstr_method ) \
254+ 	(((zend_function *)zend_hash_find_ptr(&(class_type)->function_table, ZSTR_KNOWN(zstr_method)))->common.scope != spl_ce_SplObjectStorage)
255+ 
192256static  zend_object  * spl_object_storage_new_ex (zend_class_entry  * class_type , zend_object  * orig ) /* {{{ */ 
193257{
194258	spl_SplObjectStorage  * intern ;
@@ -207,10 +271,27 @@ static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend
207271
208272	while  (parent ) {
209273		if  (parent  ==  spl_ce_SplObjectStorage ) {
274+ 			/* Possible optimization: Cache these results with a map from class entry to IS_NULL/IS_PTR. 
275+ 			 * Or maybe just a single item with the result for the most recently loaded subclass. */ 
210276			if  (class_type  !=  spl_ce_SplObjectStorage ) {
211- 				intern -> fptr_get_hash  =  zend_hash_str_find_ptr (& class_type -> function_table , "gethash" , sizeof ("gethash" ) -  1 );
212- 				if  (intern -> fptr_get_hash -> common .scope  ==  spl_ce_SplObjectStorage ) {
213- 					intern -> fptr_get_hash  =  NULL ;
277+ 				zend_function  * get_hash  =  zend_hash_str_find_ptr (& class_type -> function_table , "gethash" , sizeof ("gethash" ) -  1 );
278+ 				if  (get_hash -> common .scope  !=  spl_ce_SplObjectStorage ) {
279+ 					intern -> fptr_get_hash  =  get_hash ;
280+ 				}
281+ 				if  (intern -> fptr_get_hash  !=  NULL  || 
282+ 					SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETGET ) || 
283+ 					SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETEXISTS )) {
284+ 					intern -> flags  |= SOS_OVERRIDDEN_READ_DIMENSION ;
285+ 				}
286+ 
287+ 				if  (intern -> fptr_get_hash  !=  NULL  || 
288+ 					SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETSET )) {
289+ 					intern -> flags  |= SOS_OVERRIDDEN_WRITE_DIMENSION ;
290+ 				}
291+ 
292+ 				if  (intern -> fptr_get_hash  !=  NULL  || 
293+ 					SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETUNSET )) {
294+ 					intern -> flags  |= SOS_OVERRIDDEN_UNSET_DIMENSION ;
214295				}
215296			}
216297			break ;
@@ -328,20 +409,21 @@ static zend_object *spl_SplObjectStorage_new(zend_class_entry *class_type)
328409}
329410/* }}} */ 
330411
331- int  spl_object_storage_contains (spl_SplObjectStorage  * intern , zend_object  * obj ) /* {{{ */ 
412+ /* Returns true if the SplObjectStorage contains an entry for getHash(obj), even if the corresponding value is null. */ 
413+ bool  spl_object_storage_contains (spl_SplObjectStorage  * intern , zend_object  * obj ) /* {{{ */ 
332414{
333- 	int  found ;
415+ 	if  (EXPECTED (!intern -> fptr_get_hash )) {
416+ 		return  zend_hash_index_find (& intern -> storage , obj -> handle ) !=  NULL ;
417+ 	}
334418	zend_hash_key  key ;
335419	if  (spl_object_storage_get_hash (& key , intern , obj ) ==  FAILURE ) {
336- 		return  0 ;
420+ 		return  true ;
337421	}
338422
339- 	if  (key .key ) {
340- 		found  =  zend_hash_exists (& intern -> storage , key .key );
341- 	} else  {
342- 		found  =  zend_hash_index_exists (& intern -> storage , key .h );
343- 	}
344- 	spl_object_storage_free_hash (intern , & key );
423+ 	ZEND_ASSERT (key .key );
424+ 	bool  found  =  zend_hash_exists (& intern -> storage , key .key );
425+ 	zend_string_release_ex (key .key , 0 );
426+ 
345427	return  found ;
346428} /* }}} */ 
347429
@@ -361,6 +443,69 @@ PHP_METHOD(SplObjectStorage, attach)
361443	spl_object_storage_attach (intern , obj , inf );
362444} /* }}} */ 
363445
446+ static  int  spl_object_storage_has_dimension (zend_object  * object , zval  * offset , int  check_empty )
447+ {
448+ 	spl_SplObjectStorage  * intern  =  spl_object_storage_from_obj (object );
449+ 	if  (UNEXPECTED (offset  ==  NULL  ||  Z_TYPE_P (offset ) !=  IS_OBJECT  ||  (intern -> flags  &  SOS_OVERRIDDEN_READ_DIMENSION ))) {
450+ 		/* Can't optimize empty()/isset() check if getHash, offsetExists, or offsetGet is overridden */ 
451+ 		return  zend_std_has_dimension (object , offset , check_empty );
452+ 	}
453+ 	spl_SplObjectStorageElement  * element  =  zend_hash_index_find_ptr (& intern -> storage , Z_OBJ_HANDLE_P (offset ));
454+ 	if  (!element ) {
455+ 		return  0 ;
456+ 	}
457+ 
458+ 	if  (check_empty ) {
459+ 		return  i_zend_is_true (& element -> inf );
460+ 	}
461+ 	/* NOTE: SplObjectStorage->offsetExists() is an alias of SplObjectStorage->contains(), so this returns true even if the value is null. */ 
462+ 	return  1 ;
463+ }
464+ 
465+ static  zval  * spl_object_storage_read_dimension (zend_object  * object , zval  * offset , int  type , zval  * rv )
466+ {
467+ 	spl_SplObjectStorage  * intern  =  spl_object_storage_from_obj (object );
468+ 	if  (UNEXPECTED (offset  ==  NULL  ||  Z_TYPE_P (offset ) !=  IS_OBJECT  ||  (intern -> flags  &  SOS_OVERRIDDEN_READ_DIMENSION ))) {
469+ 		/* Can't optimize it if getHash, offsetExists, or offsetGet is overridden */ 
470+ 		return  zend_std_read_dimension (object , offset , type , rv );
471+ 	}
472+ 	spl_SplObjectStorageElement  * element  =  zend_hash_index_find_ptr (& intern -> storage , Z_OBJ_HANDLE_P (offset ));
473+ 
474+ 	if  (!element ) {
475+ 		if  (type  ==  BP_VAR_IS ) {
476+ 			return  & EG (uninitialized_zval );
477+ 		}
478+ 		zend_throw_exception_ex (spl_ce_UnexpectedValueException , 0 , "Object not found" );
479+ 		return  NULL ;
480+ 	} else  {
481+ 		/* This deliberately returns a non-reference, even for BP_VAR_W and BP_VAR_RW, to behave the same way as SplObjectStorage did when using the default zend_std_read_dimension behavior. 
482+ 		 * i.e. This prevents taking a reference to an entry of SplObjectStorage because offsetGet would return a non-reference. */ 
483+ 		ZEND_ASSERT (Z_TYPE (element -> inf ) !=  IS_REFERENCE );
484+ 		ZVAL_COPY (rv , & element -> inf );
485+ 		return  rv ;
486+ 	}
487+ }
488+ 
489+ static  void  spl_object_storage_write_dimension (zend_object  * object , zval  * offset , zval  * inf )
490+ {
491+ 	spl_SplObjectStorage  * intern  =  spl_object_storage_from_obj (object );
492+ 	if  (UNEXPECTED (offset  ==  NULL  ||  Z_TYPE_P (offset ) !=  IS_OBJECT  ||  (intern -> flags  &  SOS_OVERRIDDEN_WRITE_DIMENSION ))) {
493+ 		zend_std_write_dimension (object , offset , inf );
494+ 		return ;
495+ 	}
496+ 	spl_object_storage_attach_handle (intern , Z_OBJ_P (offset ), inf );
497+ }
498+ 
499+ static  void  spl_object_storage_unset_dimension (zend_object  * object , zval  * offset )
500+ {
501+ 	spl_SplObjectStorage  * intern  =  spl_object_storage_from_obj (object );
502+ 	if  (UNEXPECTED (Z_TYPE_P (offset ) !=  IS_OBJECT  ||  (intern -> flags  &  SOS_OVERRIDDEN_UNSET_DIMENSION ))) {
503+ 		zend_std_unset_dimension (object , offset );
504+ 		return ;
505+ 	}
506+ 	zend_hash_index_del (& intern -> storage , Z_OBJ_HANDLE_P (offset ));
507+ }
508+ 
364509/* {{{ Detaches an object from the storage */ 
365510PHP_METHOD (SplObjectStorage , detach )
366511{
@@ -411,7 +556,7 @@ PHP_METHOD(SplObjectStorage, offsetGet)
411556	if  (!element ) {
412557		zend_throw_exception_ex (spl_ce_UnexpectedValueException , 0 , "Object not found" );
413558	} else  {
414- 		RETURN_COPY_DEREF (& element -> inf );
559+ 		RETURN_COPY (& element -> inf );
415560	}
416561} /* }}} */ 
417562
@@ -1201,6 +1346,10 @@ PHP_MINIT_FUNCTION(spl_observer)
12011346	spl_handler_SplObjectStorage .clone_obj        =  spl_object_storage_clone ;
12021347	spl_handler_SplObjectStorage .get_gc           =  spl_object_storage_get_gc ;
12031348	spl_handler_SplObjectStorage .free_obj         =  spl_SplObjectStorage_free_storage ;
1349+ 	spl_handler_SplObjectStorage .read_dimension   =  spl_object_storage_read_dimension ;
1350+ 	spl_handler_SplObjectStorage .write_dimension  =  spl_object_storage_write_dimension ;
1351+ 	spl_handler_SplObjectStorage .has_dimension    =  spl_object_storage_has_dimension ;
1352+ 	spl_handler_SplObjectStorage .unset_dimension  =  spl_object_storage_unset_dimension ;
12041353
12051354	spl_ce_MultipleIterator  =  register_class_MultipleIterator (zend_ce_iterator );
12061355	spl_ce_MultipleIterator -> create_object  =  spl_SplObjectStorage_new ;
0 commit comments