Skip to content

Commit b3c4b2f

Browse files
Address review: flat C arrays, zend_hash_find_known_hash, DC_MASK_IS_*, Serializable test
- Use zend_hash_find_known_hash() for all interned dc_key_* lookups - Use DC_MASK_IS_NAMED_CLOSURE() in dc_mask_has_closure - Replace class_list HashTable with flat zend_string** array - Replace ce_cache HashTable with flat zend_class_entry** array (indexed by class_id, parallel to class_names) - Replace objects HashTable with flat zval* array (indexed by obj_id) - Update dc_resolve() signature to take zval* + num_objects - Add deepclone_serializable.phpt covering the Serializable code path (class[1] == ':' branch that was previously untested)
1 parent 35e026e commit b3c4b2f

2 files changed

Lines changed: 144 additions & 93 deletions

File tree

deepclone.c

Lines changed: 102 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -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

25552559
cleanup:
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

Comments
 (0)