@@ -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,54 @@ 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+ if (inf ) {
162+ ZVAL_COPY (& pelement -> inf , inf );
163+ } else {
164+ ZVAL_NULL (& pelement -> inf );
165+ }
166+ /* Call the old value's destructor last, in case it moves the entry */
167+ zval_ptr_dtor (& zv_inf );
168+ return pelement ;
169+ }
170+
171+ pelement = spl_object_storage_create_element (obj , inf );
172+ ZVAL_PTR (entry_zv , pelement );
173+ return pelement ;
174+ } /* }}} */
175+
128176spl_SplObjectStorageElement * spl_object_storage_attach (spl_SplObjectStorage * intern , zend_object * obj , zval * inf ) /* {{{ */
129177{
178+ if (EXPECTED (!(intern -> flags & SOS_OVERRIDDEN_WRITE_DIMENSION ))) {
179+ return spl_object_storage_attach_handle (intern , obj , inf );
180+ }
181+ /* getHash or offsetSet is overridden. */
182+
130183 spl_SplObjectStorageElement * pelement , element ;
131184 zend_hash_key key ;
132185 if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
@@ -136,13 +189,16 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
136189 pelement = spl_object_storage_get (intern , & key );
137190
138191 if (pelement ) {
139- zval_ptr_dtor (& pelement -> inf );
192+ zval zv_inf ;
193+ ZVAL_COPY_VALUE (& zv_inf , & pelement -> inf );
140194 if (inf ) {
141195 ZVAL_COPY (& pelement -> inf , inf );
142196 } else {
143197 ZVAL_NULL (& pelement -> inf );
144198 }
145199 spl_object_storage_free_hash (intern , & key );
200+ /* Call the old value's destructor last, in case it moves the entry */
201+ zval_ptr_dtor (& zv_inf );
146202 return pelement ;
147203 }
148204
@@ -164,6 +220,9 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
164220
165221static int spl_object_storage_detach (spl_SplObjectStorage * intern , zend_object * obj ) /* {{{ */
166222{
223+ if (EXPECTED (!(intern -> flags & SOS_OVERRIDDEN_UNSET_DIMENSION ))) {
224+ return zend_hash_index_del (& intern -> storage , obj -> handle );
225+ }
167226 int ret = FAILURE ;
168227 zend_hash_key key ;
169228 if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
@@ -189,6 +248,9 @@ void spl_object_storage_addall(spl_SplObjectStorage *intern, spl_SplObjectStorag
189248 intern -> index = 0 ;
190249} /* }}} */
191250
251+ #define SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , zstr_method ) \
252+ (((zend_function *)zend_hash_find_ptr(&(class_type)->function_table, ZSTR_KNOWN(zstr_method)))->common.scope != spl_ce_SplObjectStorage)
253+
192254static zend_object * spl_object_storage_new_ex (zend_class_entry * class_type , zend_object * orig ) /* {{{ */
193255{
194256 spl_SplObjectStorage * intern ;
@@ -207,10 +269,27 @@ static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend
207269
208270 while (parent ) {
209271 if (parent == spl_ce_SplObjectStorage ) {
272+ /* Possible optimization: Cache these results with a map from class entry to IS_NULL/IS_PTR.
273+ * Or maybe just a single item with the result for the most recently loaded subclass. */
210274 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 ;
275+ zend_function * get_hash = zend_hash_str_find_ptr (& class_type -> function_table , "gethash" , sizeof ("gethash" ) - 1 );
276+ if (get_hash -> common .scope != spl_ce_SplObjectStorage ) {
277+ intern -> fptr_get_hash = get_hash ;
278+ }
279+ if (intern -> fptr_get_hash != NULL ||
280+ SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETGET ) ||
281+ SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETEXISTS )) {
282+ intern -> flags |= SOS_OVERRIDDEN_READ_DIMENSION ;
283+ }
284+
285+ if (intern -> fptr_get_hash != NULL ||
286+ SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETSET )) {
287+ intern -> flags |= SOS_OVERRIDDEN_WRITE_DIMENSION ;
288+ }
289+
290+ if (intern -> fptr_get_hash != NULL ||
291+ SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE (class_type , ZEND_STR_OFFSETUNSET )) {
292+ intern -> flags |= SOS_OVERRIDDEN_UNSET_DIMENSION ;
214293 }
215294 }
216295 break ;
@@ -328,20 +407,21 @@ static zend_object *spl_SplObjectStorage_new(zend_class_entry *class_type)
328407}
329408/* }}} */
330409
331- int spl_object_storage_contains (spl_SplObjectStorage * intern , zend_object * obj ) /* {{{ */
410+ /* Returns true if the SplObjectStorage contains an entry for getHash(obj), even if the corresponding value is null. */
411+ bool spl_object_storage_contains (spl_SplObjectStorage * intern , zend_object * obj ) /* {{{ */
332412{
333- int found ;
413+ if (EXPECTED (!intern -> fptr_get_hash )) {
414+ return zend_hash_index_find (& intern -> storage , obj -> handle ) != NULL ;
415+ }
334416 zend_hash_key key ;
335417 if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
336- return 0 ;
418+ return true ;
337419 }
338420
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 );
421+ ZEND_ASSERT (key .key );
422+ bool found = zend_hash_exists (& intern -> storage , key .key );
423+ zend_string_release_ex (key .key , 0 );
424+
345425 return found ;
346426} /* }}} */
347427
@@ -361,6 +441,68 @@ PHP_METHOD(SplObjectStorage, attach)
361441 spl_object_storage_attach (intern , obj , inf );
362442} /* }}} */
363443
444+ static int spl_object_storage_has_dimension (zend_object * object , zval * offset , int check_empty )
445+ {
446+ spl_SplObjectStorage * intern = spl_object_storage_from_obj (object );
447+ if (UNEXPECTED (offset == NULL || Z_TYPE_P (offset ) != IS_OBJECT || (intern -> flags & SOS_OVERRIDDEN_READ_DIMENSION ))) {
448+ /* Can't optimize empty()/isset() check if getHash, offsetExists, or offsetGet is overridden */
449+ return zend_std_has_dimension (object , offset , check_empty );
450+ }
451+ spl_SplObjectStorageElement * element = zend_hash_index_find_ptr (& intern -> storage , Z_OBJ_HANDLE_P (offset ));
452+ if (!element ) {
453+ return 0 ;
454+ }
455+
456+ if (check_empty ) {
457+ return i_zend_is_true (& element -> inf );
458+ }
459+ /* NOTE: SplObjectStorage->offsetExists() is an alias of SplObjectStorage->contains(), so this returns true even if the value is null. */
460+ return 1 ;
461+ }
462+
463+ static zval * spl_object_storage_read_dimension (zend_object * object , zval * offset , int type , zval * rv )
464+ {
465+ spl_SplObjectStorage * intern = spl_object_storage_from_obj (object );
466+ if (UNEXPECTED (offset == NULL || Z_TYPE_P (offset ) != IS_OBJECT || (intern -> flags & SOS_OVERRIDDEN_READ_DIMENSION ))) {
467+ /* Can't optimize it if getHash, offsetExists, or offsetGet is overridden */
468+ return zend_std_read_dimension (object , offset , type , rv );
469+ }
470+ spl_SplObjectStorageElement * element = zend_hash_index_find_ptr (& intern -> storage , Z_OBJ_HANDLE_P (offset ));
471+
472+ if (!element ) {
473+ if (type == BP_VAR_IS ) {
474+ return & EG (uninitialized_zval );
475+ }
476+ zend_throw_exception_ex (spl_ce_UnexpectedValueException , 0 , "Object not found" );
477+ return NULL ;
478+ } else {
479+ /* 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.
480+ * i.e. This prevents taking a reference to an entry of SplObjectStorage because offsetGet would return a non-reference. */
481+ ZVAL_COPY_DEREF (rv , & element -> inf );
482+ return rv ;
483+ }
484+ }
485+
486+ static void spl_object_storage_write_dimension (zend_object * object , zval * offset , zval * inf )
487+ {
488+ spl_SplObjectStorage * intern = spl_object_storage_from_obj (object );
489+ if (UNEXPECTED (offset == NULL || Z_TYPE_P (offset ) != IS_OBJECT || (intern -> flags & SOS_OVERRIDDEN_WRITE_DIMENSION ))) {
490+ zend_std_write_dimension (object , offset , inf );
491+ return ;
492+ }
493+ spl_object_storage_attach_handle (intern , Z_OBJ_P (offset ), inf );
494+ }
495+
496+ static void spl_object_storage_unset_dimension (zend_object * object , zval * offset )
497+ {
498+ spl_SplObjectStorage * intern = spl_object_storage_from_obj (object );
499+ if (UNEXPECTED (Z_TYPE_P (offset ) != IS_OBJECT || (intern -> flags & SOS_OVERRIDDEN_UNSET_DIMENSION ))) {
500+ zend_std_unset_dimension (object , offset );
501+ return ;
502+ }
503+ zend_hash_index_del (& intern -> storage , Z_OBJ_HANDLE_P (offset ));
504+ }
505+
364506/* {{{ Detaches an object from the storage */
365507PHP_METHOD (SplObjectStorage , detach )
366508{
@@ -851,6 +993,7 @@ PHP_METHOD(SplObjectStorage, __unserialize)
851993 RETURN_THROWS ();
852994 }
853995
996+ ZVAL_DEREF (val );
854997 spl_object_storage_attach (intern , Z_OBJ_P (key ), val );
855998 key = NULL ;
856999 } else {
@@ -1201,6 +1344,10 @@ PHP_MINIT_FUNCTION(spl_observer)
12011344 spl_handler_SplObjectStorage .clone_obj = spl_object_storage_clone ;
12021345 spl_handler_SplObjectStorage .get_gc = spl_object_storage_get_gc ;
12031346 spl_handler_SplObjectStorage .free_obj = spl_SplObjectStorage_free_storage ;
1347+ spl_handler_SplObjectStorage .read_dimension = spl_object_storage_read_dimension ;
1348+ spl_handler_SplObjectStorage .write_dimension = spl_object_storage_write_dimension ;
1349+ spl_handler_SplObjectStorage .has_dimension = spl_object_storage_has_dimension ;
1350+ spl_handler_SplObjectStorage .unset_dimension = spl_object_storage_unset_dimension ;
12041351
12051352 spl_ce_MultipleIterator = register_class_MultipleIterator (zend_ce_iterator );
12061353 spl_ce_MultipleIterator -> create_object = spl_SplObjectStorage_new ;
0 commit comments