@@ -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,55 @@ 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
+ 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
+
128
177
spl_SplObjectStorageElement * spl_object_storage_attach (spl_SplObjectStorage * intern , zend_object * obj , zval * inf ) /* {{{ */
129
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
+
130
184
spl_SplObjectStorageElement * pelement , element ;
131
185
zend_hash_key key ;
132
186
if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
@@ -136,13 +190,17 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
136
190
pelement = spl_object_storage_get (intern , & key );
137
191
138
192
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 );
140
196
if (inf ) {
141
197
ZVAL_COPY (& pelement -> inf , inf );
142
198
} else {
143
199
ZVAL_NULL (& pelement -> inf );
144
200
}
145
201
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 );
146
204
return pelement ;
147
205
}
148
206
@@ -164,6 +222,9 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
164
222
165
223
static int spl_object_storage_detach (spl_SplObjectStorage * intern , zend_object * obj ) /* {{{ */
166
224
{
225
+ if (EXPECTED (!(intern -> flags & SOS_OVERRIDDEN_UNSET_DIMENSION ))) {
226
+ return zend_hash_index_del (& intern -> storage , obj -> handle );
227
+ }
167
228
int ret = FAILURE ;
168
229
zend_hash_key key ;
169
230
if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
@@ -189,6 +250,9 @@ void spl_object_storage_addall(spl_SplObjectStorage *intern, spl_SplObjectStorag
189
250
intern -> index = 0 ;
190
251
} /* }}} */
191
252
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
+
192
256
static zend_object * spl_object_storage_new_ex (zend_class_entry * class_type , zend_object * orig ) /* {{{ */
193
257
{
194
258
spl_SplObjectStorage * intern ;
@@ -207,10 +271,27 @@ static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend
207
271
208
272
while (parent ) {
209
273
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. */
210
276
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 ;
214
295
}
215
296
}
216
297
break ;
@@ -328,20 +409,21 @@ static zend_object *spl_SplObjectStorage_new(zend_class_entry *class_type)
328
409
}
329
410
/* }}} */
330
411
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 ) /* {{{ */
332
414
{
333
- int found ;
415
+ if (EXPECTED (!intern -> fptr_get_hash )) {
416
+ return zend_hash_index_find (& intern -> storage , obj -> handle ) != NULL ;
417
+ }
334
418
zend_hash_key key ;
335
419
if (spl_object_storage_get_hash (& key , intern , obj ) == FAILURE ) {
336
- return 0 ;
420
+ return true ;
337
421
}
338
422
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
+
345
427
return found ;
346
428
} /* }}} */
347
429
@@ -361,6 +443,69 @@ PHP_METHOD(SplObjectStorage, attach)
361
443
spl_object_storage_attach (intern , obj , inf );
362
444
} /* }}} */
363
445
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
+
364
509
/* {{{ Detaches an object from the storage */
365
510
PHP_METHOD (SplObjectStorage , detach )
366
511
{
@@ -411,7 +556,7 @@ PHP_METHOD(SplObjectStorage, offsetGet)
411
556
if (!element ) {
412
557
zend_throw_exception_ex (spl_ce_UnexpectedValueException , 0 , "Object not found" );
413
558
} else {
414
- RETURN_COPY_DEREF (& element -> inf );
559
+ RETURN_COPY (& element -> inf );
415
560
}
416
561
} /* }}} */
417
562
@@ -1201,6 +1346,10 @@ PHP_MINIT_FUNCTION(spl_observer)
1201
1346
spl_handler_SplObjectStorage .clone_obj = spl_object_storage_clone ;
1202
1347
spl_handler_SplObjectStorage .get_gc = spl_object_storage_get_gc ;
1203
1348
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 ;
1204
1353
1205
1354
spl_ce_MultipleIterator = register_class_MultipleIterator (zend_ce_iterator );
1206
1355
spl_ce_MultipleIterator -> create_object = spl_SplObjectStorage_new ;
0 commit comments