@@ -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