@@ -549,6 +549,22 @@ static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce
549
549
}
550
550
/* }}} */
551
551
552
+ /* Because the subpattern names are persistent, and the fact that the
553
+ * symbol table destruction is skipped when using fast_shutdown,
554
+ * this means the refcounts will not be updated for the destruction of
555
+ * the arrays that hold the subpattern name keys.
556
+ * To solve this, detect this situation and duplicate the strings. */
557
+ static zend_always_inline bool has_symbol_table_hazard (void )
558
+ {
559
+ zend_execute_data * ex = EG (current_execute_data );
560
+ if (UNEXPECTED (!ex || !ex -> prev_execute_data )) {
561
+ return false;
562
+ }
563
+ /* Note: we may also narrow this to only check if the symbol table equals
564
+ * &EG(symbol_table), but this is a cheaper check. */
565
+ return ZEND_CALL_INFO (ex -> prev_execute_data ) & ZEND_CALL_HAS_SYMBOL_TABLE ;
566
+ }
567
+
552
568
/* {{{ static calculate_unit_length */
553
569
/* Calculates the byte length of the next character. Assumes valid UTF-8 for PCRE2_UTF. */
554
570
static zend_always_inline size_t calculate_unit_length (pcre_cache_entry * pce , const char * start )
@@ -971,12 +987,26 @@ static zend_always_inline void populate_match_value(
971
987
972
988
static inline void add_named (
973
989
HashTable * const subpats , zend_string * name , zval * val , bool unmatched ) {
990
+ ZEND_ASSERT (GC_FLAGS (name ) & IS_STR_PERSISTENT );
991
+
992
+ bool symbol_table_hazard = has_symbol_table_hazard ();
993
+
974
994
/* If the DUPNAMES option is used, multiple subpatterns might have the same name.
975
995
* In this case we want to preserve the one that actually has a value. */
976
996
if (!unmatched ) {
977
- zend_hash_update (subpats , name , val );
997
+ if (EXPECTED (!symbol_table_hazard )) {
998
+ zend_hash_update (subpats , name , val );
999
+ } else {
1000
+ zend_hash_str_update (subpats , ZSTR_VAL (name ), ZSTR_LEN (name ), val );
1001
+ }
978
1002
} else {
979
- if (!zend_hash_add (subpats , name , val )) {
1003
+ zval * zv ;
1004
+ if (EXPECTED (!symbol_table_hazard )) {
1005
+ zv = zend_hash_add (subpats , name , val );
1006
+ } else {
1007
+ zv = zend_hash_str_add (subpats , ZSTR_VAL (name ), ZSTR_LEN (name ), val );
1008
+ }
1009
+ if (!zv ) {
980
1010
return ;
981
1011
}
982
1012
}
@@ -1061,10 +1091,16 @@ static void populate_subpat_array(
1061
1091
zend_hash_next_index_insert_new (subpats_ht , & val );
1062
1092
}
1063
1093
if (unmatched_as_null ) {
1094
+ bool symbol_table_hazard = has_symbol_table_hazard ();
1095
+
1064
1096
for (i = count ; i < num_subpats ; i ++ ) {
1065
1097
ZVAL_NULL (& val );
1066
1098
if (subpat_names [i ]) {
1067
- zend_hash_add (subpats_ht , subpat_names [i ], & val );
1099
+ if (EXPECTED (!symbol_table_hazard )) {
1100
+ zend_hash_add (subpats_ht , subpat_names [i ], & val );
1101
+ } else {
1102
+ zend_hash_str_add (subpats_ht , ZSTR_VAL (subpat_names [i ]), ZSTR_LEN (subpat_names [i ]), & val );
1103
+ }
1068
1104
}
1069
1105
zend_hash_next_index_insert_new (subpats_ht , & val );
1070
1106
}
@@ -1429,11 +1465,17 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str,
1429
1465
/* Add the match sets to the output array and clean up */
1430
1466
if (match_sets ) {
1431
1467
if (subpat_names ) {
1468
+ bool symbol_table_hazard = has_symbol_table_hazard ();
1469
+
1432
1470
for (i = 0 ; i < num_subpats ; i ++ ) {
1433
1471
zval wrapper ;
1434
1472
ZVAL_ARR (& wrapper , match_sets [i ]);
1435
1473
if (subpat_names [i ]) {
1436
- zend_hash_update (Z_ARRVAL_P (subpats ), subpat_names [i ], & wrapper );
1474
+ if (EXPECTED (!symbol_table_hazard )) {
1475
+ zend_hash_update (Z_ARRVAL_P (subpats ), subpat_names [i ], & wrapper );
1476
+ } else {
1477
+ zend_hash_str_update (Z_ARRVAL_P (subpats ), ZSTR_VAL (subpat_names [i ]), ZSTR_LEN (subpat_names [i ]), & wrapper );
1478
+ }
1437
1479
GC_ADDREF (match_sets [i ]);
1438
1480
}
1439
1481
zend_hash_next_index_insert_new (Z_ARRVAL_P (subpats ), & wrapper );
0 commit comments