@@ -44,10 +44,17 @@ PHPAPI zend_class_entry *spl_ce_MultipleIterator;
44
44
45
45
PHPAPI zend_object_handlers spl_handler_SplObjectStorage ;
46
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
+
47
52
typedef struct _spl_SplObjectStorage { /* {{{ */
48
53
HashTable storage ;
49
54
zend_long index ;
50
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. */
51
58
zend_long flags ;
52
59
zend_function * fptr_get_hash ;
53
60
zend_object std ;
@@ -76,7 +83,7 @@ void spl_SplObjectStorage_free_storage(zend_object *object) /* {{{ */
76
83
} /* }}} */
77
84
78
85
static 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 ) ) {
80
87
zval param ;
81
88
zval rv ;
82
89
ZVAL_OBJ (& param , obj );
@@ -125,8 +132,54 @@ static spl_SplObjectStorageElement* spl_object_storage_get(spl_SplObjectStorage
125
132
}
126
133
} /* }}} */
127
134
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
+
128
176
spl_SplObjectStorageElement * spl_object_storage_attach (spl_SplObjectStorage * intern , zend_object * obj , zval * inf ) /* {{{ */
129
177
{
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
+
130
183
spl_SplObjectStorageElement * pelement , element ;
131
184
zend_hash_key key ;
132
185
if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
@@ -136,13 +189,16 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
136
189
pelement = spl_object_storage_get (intern , & key );
137
190
138
191
if (pelement ) {
139
- zval_ptr_dtor (& pelement -> inf );
192
+ zval zv_inf ;
193
+ ZVAL_COPY_VALUE (& zv_inf , & pelement -> inf );
140
194
if (inf ) {
141
195
ZVAL_COPY (& pelement -> inf , inf );
142
196
} else {
143
197
ZVAL_NULL (& pelement -> inf );
144
198
}
145
199
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 );
146
202
return pelement ;
147
203
}
148
204
@@ -164,6 +220,9 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
164
220
165
221
static int spl_object_storage_detach (spl_SplObjectStorage * intern , zend_object * obj ) /* {{{ */
166
222
{
223
+ if (EXPECTED (!(intern -> flags & SOS_OVERRIDDEN_UNSET_DIMENSION ))) {
224
+ return zend_hash_index_del (& intern -> storage , obj -> handle );
225
+ }
167
226
int ret = FAILURE ;
168
227
zend_hash_key key ;
169
228
if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
@@ -189,6 +248,9 @@ void spl_object_storage_addall(spl_SplObjectStorage *intern, spl_SplObjectStorag
189
248
intern -> index = 0 ;
190
249
} /* }}} */
191
250
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
+
192
254
static zend_object * spl_object_storage_new_ex (zend_class_entry * class_type , zend_object * orig ) /* {{{ */
193
255
{
194
256
spl_SplObjectStorage * intern ;
@@ -207,10 +269,27 @@ static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend
207
269
208
270
while (parent ) {
209
271
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. */
210
274
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 ;
214
293
}
215
294
}
216
295
break ;
@@ -328,20 +407,21 @@ static zend_object *spl_SplObjectStorage_new(zend_class_entry *class_type)
328
407
}
329
408
/* }}} */
330
409
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 ) /* {{{ */
332
412
{
333
- int found ;
413
+ if (EXPECTED (!intern -> fptr_get_hash )) {
414
+ return zend_hash_index_find (& intern -> storage , obj -> handle ) != NULL ;
415
+ }
334
416
zend_hash_key key ;
335
417
if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
336
- return 0 ;
418
+ return true ;
337
419
}
338
420
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
+
345
425
return found ;
346
426
} /* }}} */
347
427
@@ -361,6 +441,68 @@ PHP_METHOD(SplObjectStorage, attach)
361
441
spl_object_storage_attach (intern , obj , inf );
362
442
} /* }}} */
363
443
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
+
364
506
/* {{{ Detaches an object from the storage */
365
507
PHP_METHOD (SplObjectStorage , detach )
366
508
{
@@ -851,6 +993,7 @@ PHP_METHOD(SplObjectStorage, __unserialize)
851
993
RETURN_THROWS ();
852
994
}
853
995
996
+ ZVAL_DEREF (val );
854
997
spl_object_storage_attach (intern , Z_OBJ_P (key ), val );
855
998
key = NULL ;
856
999
} else {
@@ -1201,6 +1344,10 @@ PHP_MINIT_FUNCTION(spl_observer)
1201
1344
spl_handler_SplObjectStorage .clone_obj = spl_object_storage_clone ;
1202
1345
spl_handler_SplObjectStorage .get_gc = spl_object_storage_get_gc ;
1203
1346
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 ;
1204
1351
1205
1352
spl_ce_MultipleIterator = register_class_MultipleIterator (zend_ce_iterator );
1206
1353
spl_ce_MultipleIterator -> create_object = spl_SplObjectStorage_new ;
0 commit comments