@@ -1809,7 +1809,7 @@ PHP_FUNCTION(deepclone_to_array)
18091809 * array nested mask → recurse into the array's elements
18101810 * other no marker → copy value as-is
18111811 */
1812- static void dc_resolve (zval * value , zval * mask , zval * objects , HashTable * refs , zval * retval )
1812+ static void dc_resolve (zval * value , zval * mask , zval * objects , uint32_t num_objects , HashTable * refs , zval * retval )
18131813{
18141814 if (EXPECTED (DC_MASK_IS_OBJ_REF (mask ))) {
18151815 if (UNEXPECTED (Z_TYPE_P (value ) != IS_LONG )) {
@@ -1819,11 +1819,11 @@ static void dc_resolve(zval *value, zval *mask, zval *objects, HashTable *refs,
18191819 zend_long id = Z_LVAL_P (value );
18201820 zval * target ;
18211821 if (EXPECTED (id >= 0 )) {
1822- target = zend_hash_index_find (Z_ARRVAL_P (objects ), id );
1823- if (UNEXPECTED (!target )) {
1822+ if (UNEXPECTED ((zend_ulong ) id >= num_objects )) {
18241823 zend_value_error ("deepclone_from_array(): malformed payload, unknown object id " ZEND_LONG_FMT , id );
18251824 return ;
18261825 }
1826+ target = & objects [id ];
18271827 } else {
18281828 target = zend_hash_index_find (refs , - id );
18291829 if (UNEXPECTED (!target )) {
@@ -1902,13 +1902,17 @@ static void dc_resolve(zval *value, zval *mask, zval *objects, HashTable *refs,
19021902 zend_long id = Z_LVAL_P (zobj );
19031903 zval * target ;
19041904 if (id >= 0 ) {
1905- target = zend_hash_index_find (Z_ARRVAL_P (objects ), id );
1905+ if ((zend_ulong ) id >= num_objects ) {
1906+ zend_value_error ("deepclone_from_array(): malformed payload, named-closure references unknown id " ZEND_LONG_FMT , id );
1907+ return ;
1908+ }
1909+ target = & objects [id ];
19061910 } else {
19071911 target = zend_hash_index_find (refs , - id );
1908- }
1909- if (! target ) {
1910- zend_value_error ( "deepclone_from_array(): malformed payload, named-closure references unknown id " ZEND_LONG_FMT , id ) ;
1911- return ;
1912+ if (! target ) {
1913+ zend_value_error ( "deepclone_from_array(): malformed payload, named-closure references unknown id " ZEND_LONG_FMT , id );
1914+ return ;
1915+ }
19121916 }
19131917 ZVAL_COPY (& resolved_obj , target );
19141918 } else {
@@ -2037,7 +2041,7 @@ static void dc_resolve(zval *value, zval *mask, zval *objects, HashTable *refs,
20372041 } else {
20382042 zval resolved ;
20392043 ZVAL_UNDEF (& resolved );
2040- dc_resolve (slot , mval , objects , refs , & resolved );
2044+ dc_resolve (slot , mval , objects , num_objects , refs , & resolved );
20412045 if (EG (exception )) {
20422046 zval_ptr_dtor (& result );
20432047 return ;
@@ -2066,7 +2070,7 @@ static bool dc_mask_has_closure(zval *mask)
20662070 if (mask == NULL ) {
20672071 return false;
20682072 }
2069- if (Z_TYPE_P (mask ) == IS_LONG && Z_LVAL_P ( mask ) == 0 ) {
2073+ if (DC_MASK_IS_NAMED_CLOSURE (mask )) {
20702074 return true;
20712075 }
20722076 if (Z_TYPE_P (mask ) != IS_ARRAY ) {
@@ -2086,16 +2090,15 @@ PHP_FUNCTION(deepclone_from_array)
20862090 HashTable * data_ht ;
20872091 HashTable * allowed_ht = NULL ;
20882092 HashTable * allowed_set = NULL ;
2089- HashTable class_list ;
2090- HashTable ce_cache ;
20912093 HashTable refs ;
2092- zval objects ;
2094+ zend_string * * class_names = NULL ;
2095+ uint32_t num_classes = 0 ;
2096+ zend_class_entry * * class_ces = NULL ;
2097+ zval * objects = NULL ;
2098+ uint32_t num_objects = 0 ;
20932099 zend_string * * obj_classes = NULL ;
20942100 int * obj_wakeups = NULL ;
2095- bool class_list_inited = false;
2096- bool ce_cache_inited = false;
20972101 bool refs_inited = false;
2098- bool objects_inited = false;
20992102
21002103 ZEND_PARSE_PARAMETERS_START (1 , 2 )
21012104 Z_PARAM_ARRAY_HT (data_ht )
@@ -2104,22 +2107,22 @@ PHP_FUNCTION(deepclone_from_array)
21042107 ZEND_PARSE_PARAMETERS_END ();
21052108
21062109 /* Static value: return data['value'] */
2107- zval * zvalue = zend_hash_find (data_ht , dc_key_value );
2110+ zval * zvalue = zend_hash_find_known_hash (data_ht , dc_key_value );
21082111 if (zvalue ) {
21092112 ZVAL_COPY (return_value , zvalue );
21102113 return ;
21112114 }
21122115
21132116 /* ── Parse and validate the format ─────────── */
2114- zval * zclasses = zend_hash_find (data_ht , dc_key_classes );
2115- zval * zobject_meta = zend_hash_find (data_ht , dc_key_object_meta );
2116- zval * zprepared = zend_hash_find (data_ht , dc_key_prepared );
2117- zval * zmask = zend_hash_find (data_ht , dc_key_mask );
2118- zval * zproperties = zend_hash_find (data_ht , dc_key_properties );
2119- zval * zresolve = zend_hash_find (data_ht , dc_key_resolve );
2120- zval * zstates = zend_hash_find (data_ht , dc_key_states );
2121- zval * zrefs = zend_hash_find (data_ht , dc_key_refs );
2122- zval * zref_masks = zend_hash_find (data_ht , dc_key_ref_masks );
2117+ zval * zclasses = zend_hash_find_known_hash (data_ht , dc_key_classes );
2118+ zval * zobject_meta = zend_hash_find_known_hash (data_ht , dc_key_object_meta );
2119+ zval * zprepared = zend_hash_find_known_hash (data_ht , dc_key_prepared );
2120+ zval * zmask = zend_hash_find_known_hash (data_ht , dc_key_mask );
2121+ zval * zproperties = zend_hash_find_known_hash (data_ht , dc_key_properties );
2122+ zval * zresolve = zend_hash_find_known_hash (data_ht , dc_key_resolve );
2123+ zval * zstates = zend_hash_find_known_hash (data_ht , dc_key_states );
2124+ zval * zrefs = zend_hash_find_known_hash (data_ht , dc_key_refs );
2125+ zval * zref_masks = zend_hash_find_known_hash (data_ht , dc_key_ref_masks );
21232126
21242127 DC_REQUIRE (zclasses , "deepclone_from_array(): Argument #1 ($data) is missing required \"classes\" key" );
21252128 DC_REQUIRE (zobject_meta , "deepclone_from_array(): Argument #1 ($data) is missing required \"objectMeta\" key" );
@@ -2139,24 +2142,26 @@ PHP_FUNCTION(deepclone_from_array)
21392142 DC_REQUIRE (!zref_masks || Z_TYPE_P (zref_masks ) == IS_ARRAY ,
21402143 "deepclone_from_array(): Argument #1 ($data) \"refMasks\" must be of type array, %s given" , zend_zval_value_name (zref_masks ));
21412144
2142- /* ── Expand class names ────────────────────── */
2143- zend_hash_init (& class_list , 4 , NULL , NULL , 0 );
2144- class_list_inited = true;
2145- uint32_t num_classes = 0 ;
2146-
2145+ /* ── Expand class names into a flat C array ── */
21472146 if (Z_TYPE_P (zclasses ) == IS_STRING ) {
21482147 if (Z_STRLEN_P (zclasses ) > 0 ) {
2149- zend_hash_index_add (& class_list , 0 , zclasses );
21502148 num_classes = 1 ;
2149+ class_names = emalloc (sizeof (zend_string * ));
2150+ class_names [0 ] = Z_STR_P (zclasses );
21512151 }
21522152 } else {
2153- zval * cls ;
2154- ZEND_HASH_FOREACH_VAL (Z_ARRVAL_P (zclasses ), cls ) {
2155- if (Z_TYPE_P (cls ) != IS_STRING ) {
2156- DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"classes\" entries must be of type string, %s given" , zend_zval_value_name (cls ));
2157- }
2158- zend_hash_index_add (& class_list , num_classes ++ , cls );
2159- } ZEND_HASH_FOREACH_END ();
2153+ num_classes = zend_hash_num_elements (Z_ARRVAL_P (zclasses ));
2154+ if (num_classes ) {
2155+ class_names = emalloc (num_classes * sizeof (zend_string * ));
2156+ uint32_t i = 0 ;
2157+ zval * cls ;
2158+ ZEND_HASH_FOREACH_VAL (Z_ARRVAL_P (zclasses ), cls ) {
2159+ if (Z_TYPE_P (cls ) != IS_STRING ) {
2160+ DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"classes\" entries must be of type string, %s given" , zend_zval_value_name (cls ));
2161+ }
2162+ class_names [i ++ ] = Z_STR_P (cls );
2163+ } ZEND_HASH_FOREACH_END ();
2164+ }
21602165 }
21612166
21622167 /* ── Validate allowed classes ──────────────── */
@@ -2167,9 +2172,8 @@ PHP_FUNCTION(deepclone_from_array)
21672172 }
21682173
21692174 for (uint32_t i = 0 ; i < num_classes ; i ++ ) {
2170- zval * cls = zend_hash_index_find (& class_list , i );
2171- if (!dc_class_allowed (allowed_set , Z_STR_P (cls ))) {
2172- DC_INVALID ("deepclone_from_array(): class \"%s\" is not allowed" , ZSTR_VAL (Z_STR_P (cls )));
2175+ if (!dc_class_allowed (allowed_set , class_names [i ])) {
2176+ DC_INVALID ("deepclone_from_array(): class \"%s\" is not allowed" , ZSTR_VAL (class_names [i ]));
21732177 }
21742178 }
21752179
@@ -2215,9 +2219,7 @@ PHP_FUNCTION(deepclone_from_array)
22152219 }
22162220 }
22172221
2218- /* ── Build objectMeta: id → [class_name, wakeup] ── */
2219- uint32_t num_objects = 0 ;
2220-
2222+ /* ── Build objectMeta ── */
22212223 if (Z_TYPE_P (zobject_meta ) == IS_LONG ) {
22222224 zend_long n = Z_LVAL_P (zobject_meta );
22232225 if (n < 0 ) {
@@ -2236,11 +2238,10 @@ PHP_FUNCTION(deepclone_from_array)
22362238 if (num_classes < 1 ) {
22372239 DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"objectMeta\" references class index 0 but \"classes\" is empty" );
22382240 }
2239- zval * cls0 = zend_hash_index_find (& class_list , 0 );
22402241 obj_classes = emalloc (num_objects * sizeof (zend_string * ));
22412242 obj_wakeups = ecalloc (num_objects , sizeof (int ));
22422243 for (uint32_t i = 0 ; i < num_objects ; i ++ ) {
2243- obj_classes [i ] = Z_STR_P ( cls0 ) ;
2244+ obj_classes [i ] = class_names [ 0 ] ;
22442245 }
22452246 }
22462247 } else {
@@ -2272,22 +2273,26 @@ PHP_FUNCTION(deepclone_from_array)
22722273 if (cidx_val < 0 || (zend_ulong ) cidx_val >= num_classes ) {
22732274 DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"objectMeta\" entry " ZEND_ULONG_FMT " has out-of-range class index " ZEND_LONG_FMT , id , cidx_val );
22742275 }
2275- zval * cls = zend_hash_index_find (& class_list , cidx_val );
2276- obj_classes [id ] = Z_STR_P (cls );
2276+ obj_classes [id ] = class_names [cidx_val ];
22772277 } ZEND_HASH_FOREACH_END ();
22782278 }
22792279
22802280 /* ── Resolve class entries (once per unique class) ── */
2281- zend_hash_init (& ce_cache , num_classes , NULL , NULL , 0 );
2282- ce_cache_inited = true;
2281+ if (num_classes ) {
2282+ class_ces = ecalloc (num_classes , sizeof (zend_class_entry * ));
2283+ }
22832284
22842285 /* ── Initialize refs early so cleanup can always destroy it safely ── */
22852286 zend_hash_init (& refs , 4 , NULL , ZVAL_PTR_DTOR , 0 );
22862287 refs_inited = true;
22872288
22882289 /* ── Create object instances ───────────────── */
2289- array_init_size (& objects , num_objects );
2290- objects_inited = true;
2290+ if (num_objects ) {
2291+ objects = emalloc (num_objects * sizeof (zval ));
2292+ for (uint32_t i = 0 ; i < num_objects ; i ++ ) {
2293+ ZVAL_UNDEF (& objects [i ]);
2294+ }
2295+ }
22912296
22922297 for (uint32_t id = 0 ; id < num_objects ; id ++ ) {
22932298 zend_string * class_name = obj_classes [id ];
@@ -2307,27 +2312,29 @@ PHP_FUNCTION(deepclone_from_array)
23072312 }
23082313 PHP_VAR_UNSERIALIZE_DESTROY (var_hash );
23092314 } else {
2310- zval * cached_ce = zend_hash_find (& ce_cache , class_name );
2311- zend_class_entry * ce ;
2312- if (cached_ce ) {
2313- ce = Z_PTR_P (cached_ce );
2314- } else {
2315- ce = zend_lookup_class (class_name );
2316- if (!ce ) {
2317- zend_throw_exception_ex (dc_ce_class_not_found_exception , 0 ,
2318- "Class \"%s\" not found." , ZSTR_VAL (class_name ));
2319- goto cleanup ;
2315+ /* Look up CE, caching by class_id (parallel to class_names). */
2316+ zend_class_entry * ce = NULL ;
2317+ for (uint32_t ci = 0 ; ci < num_classes ; ci ++ ) {
2318+ if (class_names [ci ] == class_name ) {
2319+ ce = class_ces [ci ];
2320+ if (!ce ) {
2321+ ce = zend_lookup_class (class_name );
2322+ if (!ce ) {
2323+ zend_throw_exception_ex (dc_ce_class_not_found_exception , 0 ,
2324+ "Class \"%s\" not found." , ZSTR_VAL (class_name ));
2325+ goto cleanup ;
2326+ }
2327+ class_ces [ci ] = ce ;
2328+ }
2329+ break ;
23202330 }
2321- zval zce ;
2322- ZVAL_PTR (& zce , ce );
2323- zend_hash_add_new (& ce_cache , class_name , & zce );
23242331 }
23252332 if (UNEXPECTED (object_init_ex (& obj_zval , ce ) != SUCCESS )) {
23262333 goto cleanup ;
23272334 }
23282335 }
23292336
2330- zend_hash_index_add_new ( Z_ARRVAL ( objects ), id , & obj_zval );
2337+ ZVAL_COPY_VALUE ( & objects [ id ] , & obj_zval );
23312338 }
23322339
23332340 /* ── Resolve refs ──────────────────────────── */
@@ -2349,7 +2356,7 @@ PHP_FUNCTION(deepclone_from_array)
23492356 if (!slot ) continue ;
23502357 zval resolved ;
23512358 ZVAL_UNDEF (& resolved );
2352- dc_resolve (slot , rmask , & objects , & refs , & resolved );
2359+ dc_resolve (slot , rmask , objects , num_objects , & refs , & resolved );
23532360 if (EG (exception )) goto cleanup ;
23542361 /* Write through reference if slot was made into one (by dc_resolve) */
23552362 if (Z_ISREF_P (slot )) {
@@ -2388,19 +2395,16 @@ PHP_FUNCTION(deepclone_from_array)
23882395 }
23892396 }
23902397
2391- /* Get scope class entry for private/protected access (cached) */
2392- zval * cached_scope_ce = zend_hash_find (& ce_cache , scope_name );
2393- zend_class_entry * scope_ce ;
2394- if (cached_scope_ce ) {
2395- scope_ce = Z_PTR_P (cached_scope_ce );
2396- } else {
2397- scope_ce = zend_lookup_class (scope_name );
2398- if (scope_ce ) {
2399- zval zce ;
2400- ZVAL_PTR (& zce , scope_ce );
2401- zend_hash_add_new (& ce_cache , scope_name , & zce );
2398+ zend_class_entry * scope_ce = NULL ;
2399+ for (uint32_t ci = 0 ; ci < num_classes ; ci ++ ) {
2400+ if (zend_string_equals (class_names [ci ], scope_name )) {
2401+ scope_ce = class_ces [ci ];
2402+ break ;
24022403 }
24032404 }
2405+ if (!scope_ce ) {
2406+ scope_ce = zend_lookup_class (scope_name );
2407+ }
24042408 /* PHP 8.5+ made EG(fake_scope) a const pointer (#19060). The
24052409 * shim casts the read so we keep one source for both worlds. */
24062410#if PHP_VERSION_ID >= 80500
@@ -2440,14 +2444,14 @@ PHP_FUNCTION(deepclone_from_array)
24402444 zend_ulong obj_id ;
24412445 zval * prop_val ;
24422446 ZEND_HASH_FOREACH_NUM_KEY_VAL (Z_ARRVAL_P (id_values ), obj_id , prop_val ) {
2443- zval * obj_zval = zend_hash_index_find ( Z_ARRVAL ( objects ), obj_id ) ;
2444- if (! obj_zval ) continue ;
2447+ if ( obj_id >= num_objects ) continue ;
2448+ zval * obj_zval = & objects [ obj_id ] ;
24452449
24462450 zval final_val ;
24472451 zval * marker = resolve_ids ? zend_hash_index_find (resolve_ids , obj_id ) : NULL ;
24482452 if (marker ) {
24492453 ZVAL_UNDEF (& final_val );
2450- dc_resolve (prop_val , marker , & objects , & refs , & final_val );
2454+ dc_resolve (prop_val , marker , objects , num_objects , & refs , & final_val );
24512455 if (EG (exception )) {
24522456 EG (fake_scope ) = old_scope ;
24532457 goto cleanup ;
@@ -2493,15 +2497,15 @@ PHP_FUNCTION(deepclone_from_array)
24932497 if (Z_LVAL_P (zid ) < 0 || (zend_ulong ) Z_LVAL_P (zid ) >= num_objects ) {
24942498 DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"states\" entry references unknown object id " ZEND_LONG_FMT , Z_LVAL_P (zid ));
24952499 }
2496- zval * obj_zval = zend_hash_index_find ( Z_ARRVAL ( objects ), Z_LVAL_P (zid )) ;
2500+ zval * obj_zval = & objects [ Z_LVAL_P (zid )] ;
24972501 zend_class_entry * unser_ce = Z_OBJCE_P (obj_zval );
24982502 if (!unser_ce -> __unserialize ) {
24992503 DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"states\" entry references object id " ZEND_LONG_FMT " whose class %s has no __unserialize() method" , Z_LVAL_P (zid ), ZSTR_VAL (unser_ce -> name ));
25002504 }
25012505 zval resolved_props ;
25022506 if (smask ) {
25032507 ZVAL_UNDEF (& resolved_props );
2504- dc_resolve (sprops , smask , & objects , & refs , & resolved_props );
2508+ dc_resolve (sprops , smask , objects , num_objects , & refs , & resolved_props );
25052509 if (EG (exception )) goto cleanup ;
25062510 } else {
25072511 ZVAL_COPY (& resolved_props , sprops );
@@ -2515,7 +2519,7 @@ PHP_FUNCTION(deepclone_from_array)
25152519 if (Z_LVAL_P (state ) < 0 || (zend_ulong ) Z_LVAL_P (state ) >= num_objects ) {
25162520 DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"states\" entry references unknown object id " ZEND_LONG_FMT , Z_LVAL_P (state ));
25172521 }
2518- zval * obj_zval = zend_hash_index_find ( Z_ARRVAL ( objects ), Z_LVAL_P (state )) ;
2522+ zval * obj_zval = & objects [ Z_LVAL_P (state )] ;
25192523 zend_class_entry * wakeup_ce = Z_OBJCE_P (obj_zval );
25202524 zend_function * wakeup_fn = zend_hash_find_ptr (& wakeup_ce -> function_table , ZSTR_KNOWN (ZEND_STR_WAKEUP ));
25212525 if (wakeup_fn ) {
@@ -2536,7 +2540,7 @@ PHP_FUNCTION(deepclone_from_array)
25362540 if ((zend_ulong ) id >= num_objects ) {
25372541 DC_INVALID ("deepclone_from_array(): Argument #1 ($data) \"prepared\" references unknown object id " ZEND_LONG_FMT , id );
25382542 }
2539- zval * obj = zend_hash_index_find ( Z_ARRVAL ( objects ), id ) ;
2543+ zval * obj = & objects [ id ] ;
25402544 ZVAL_COPY (return_value , obj );
25412545 } else {
25422546 zval * ref = zend_hash_index_find (& refs , - id );
@@ -2546,20 +2550,25 @@ PHP_FUNCTION(deepclone_from_array)
25462550 ZVAL_COPY (return_value , ref );
25472551 }
25482552 } else if (zmask ) {
2549- dc_resolve (zprepared , zmask , & objects , & refs , return_value );
2553+ dc_resolve (zprepared , zmask , objects , num_objects , & refs , return_value );
25502554 if (EG (exception )) goto cleanup ;
25512555 } else {
25522556 ZVAL_COPY (return_value , zprepared );
25532557 }
25542558
25552559cleanup :
25562560 if (allowed_set ) { zend_hash_destroy (allowed_set ); efree (allowed_set ); }
2557- if (ce_cache_inited ) zend_hash_destroy (& ce_cache );
2558- if (refs_inited ) zend_hash_destroy (& refs );
2559- if (objects_inited ) zval_ptr_dtor (& objects );
2560- if (obj_classes ) efree (obj_classes );
2561- if (obj_wakeups ) efree (obj_wakeups );
2562- if (class_list_inited ) zend_hash_destroy (& class_list );
2561+ if (refs_inited ) zend_hash_destroy (& refs );
2562+ if (objects ) {
2563+ for (uint32_t i = 0 ; i < num_objects ; i ++ ) {
2564+ zval_ptr_dtor (& objects [i ]);
2565+ }
2566+ efree (objects );
2567+ }
2568+ if (obj_classes ) efree (obj_classes );
2569+ if (obj_wakeups ) efree (obj_wakeups );
2570+ if (class_ces ) efree (class_ces );
2571+ if (class_names ) efree (class_names );
25632572}
25642573#undef DC_INVALID
25652574
0 commit comments