1 /*
2 +----------------------------------------------------------------------+
3 | APCu |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2018 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Author: Nikita Popov <nikic@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 #include "apc.h"
20 #include "apc_cache.h"
21
22 #if PHP_VERSION_ID < 70300
23 # define GC_SET_REFCOUNT(ref, rc) (GC_REFCOUNT(ref) = (rc))
24 # define GC_ADDREF(ref) GC_REFCOUNT(ref)++
25 # define GC_SET_PERSISTENT_TYPE(ref, type) (GC_TYPE_INFO(ref) = type)
26 # if PHP_VERSION_ID < 70200
27 # define GC_ARRAY IS_ARRAY
28 # else
29 # define GC_ARRAY (IS_ARRAY | (GC_COLLECTABLE << GC_FLAGS_SHIFT))
30 # endif
31 #else
32 # define GC_SET_PERSISTENT_TYPE(ref, type) \
33 (GC_TYPE_INFO(ref) = type | (GC_PERSISTENT << GC_FLAGS_SHIFT))
34 #endif
35
36 #if PHP_VERSION_ID < 80000
37 # define GC_REFERENCE IS_REFERENCE
38 #endif
39
40 /*
41 * PERSIST: Copy from request memory to SHM.
42 */
43
44 typedef struct _apc_persist_context_t {
45 /* Serializer to use */
46 apc_serializer_t *serializer;
47 /* Computed size of the needed SMA allocation */
48 size_t size;
49 /* Whether or not we may have to memoize refcounted addresses */
50 zend_bool memoization_needed;
51 /* Whether to serialize the top-level value */
52 zend_bool use_serialization;
53 /* Serialized object/array string, in case there can only be one */
54 unsigned char *serialized_str;
55 size_t serialized_str_len;
56 /* Whole SMA allocation */
57 char *alloc;
58 /* Current position in allocation */
59 char *alloc_cur;
60 /* HashTable storing refcounteds for which the size has already been counted. */
61 HashTable already_counted;
62 /* HashTable storing already allocated refcounteds. Pointers to refcounteds are stored. */
63 HashTable already_allocated;
64 } apc_persist_context_t;
65
66 #define ADD_SIZE(sz) ctxt->size += ZEND_MM_ALIGNED_SIZE(sz)
67 #define ADD_SIZE_STR(len) ADD_SIZE(_ZSTR_STRUCT_SIZE(len))
68
69 #define ALLOC(sz) apc_persist_alloc(ctxt, sz)
70 #define COPY(val, sz) apc_persist_alloc_copy(ctxt, val, sz)
71
72 static zend_bool apc_persist_calc_zval(apc_persist_context_t *ctxt, const zval *zv);
73 static void apc_persist_copy_zval_impl(apc_persist_context_t *ctxt, zval *zv);
74
apc_persist_copy_zval(apc_persist_context_t * ctxt,zval * zv)75 static inline void apc_persist_copy_zval(apc_persist_context_t *ctxt, zval *zv) {
76 /* No data apart from the zval itself */
77 if (Z_TYPE_P(zv) < IS_STRING) {
78 return;
79 }
80
81 apc_persist_copy_zval_impl(ctxt, zv);
82 }
83
apc_persist_init_context(apc_persist_context_t * ctxt,apc_serializer_t * serializer)84 void apc_persist_init_context(apc_persist_context_t *ctxt, apc_serializer_t *serializer) {
85 ctxt->serializer = serializer;
86 ctxt->size = 0;
87 ctxt->memoization_needed = 0;
88 ctxt->use_serialization = 0;
89 ctxt->serialized_str = NULL;
90 ctxt->serialized_str_len = 0;
91 ctxt->alloc = NULL;
92 ctxt->alloc_cur = NULL;
93 }
94
apc_persist_destroy_context(apc_persist_context_t * ctxt)95 void apc_persist_destroy_context(apc_persist_context_t *ctxt) {
96 if (ctxt->memoization_needed) {
97 zend_hash_destroy(&ctxt->already_counted);
98 zend_hash_destroy(&ctxt->already_allocated);
99 }
100 if (ctxt->serialized_str) {
101 efree(ctxt->serialized_str);
102 }
103 }
104
apc_persist_calc_memoize(apc_persist_context_t * ctxt,void * ptr)105 static zend_bool apc_persist_calc_memoize(apc_persist_context_t *ctxt, void *ptr) {
106 zval tmp;
107 if (!ctxt->memoization_needed) {
108 return 0;
109 }
110
111 if (zend_hash_index_exists(&ctxt->already_counted, (uintptr_t) ptr)) {
112 return 1;
113 }
114
115 ZVAL_NULL(&tmp);
116 zend_hash_index_add_new(&ctxt->already_counted, (uintptr_t) ptr, &tmp);
117 return 0;
118 }
119
apc_persist_calc_ht(apc_persist_context_t * ctxt,const HashTable * ht)120 static zend_bool apc_persist_calc_ht(apc_persist_context_t *ctxt, const HashTable *ht) {
121 uint32_t idx;
122
123 ADD_SIZE(sizeof(HashTable));
124 if (ht->nNumUsed == 0) {
125 return 1;
126 }
127
128 /* TODO Too sparse hashtables could be compacted here */
129 ADD_SIZE(HT_USED_SIZE(ht));
130 for (idx = 0; idx < ht->nNumUsed; idx++) {
131 Bucket *p = ht->arData + idx;
132 if (Z_TYPE(p->val) == IS_UNDEF) continue;
133
134 /* This can only happen if $GLOBALS is placed in the cache.
135 * Don't bother with this edge-case, fall back to serialization. */
136 if (Z_TYPE(p->val) == IS_INDIRECT) {
137 ctxt->use_serialization = 1;
138 return 0;
139 }
140
141 /* TODO These strings can be reused
142 if (p->key && !apc_persist_calc_is_handled(ctxt, (zend_refcounted *) p->key)) {
143 ADD_SIZE_STR(ZSTR_LEN(p->key));
144 }*/
145 if (p->key) {
146 ADD_SIZE_STR(ZSTR_LEN(p->key));
147 }
148 if (!apc_persist_calc_zval(ctxt, &p->val)) {
149 return 0;
150 }
151 }
152
153 return 1;
154 }
155
apc_persist_calc_serialize(apc_persist_context_t * ctxt,const zval * zv)156 static zend_bool apc_persist_calc_serialize(apc_persist_context_t *ctxt, const zval *zv) {
157 unsigned char *buf = NULL;
158 size_t buf_len = 0;
159
160 apc_serialize_t serialize = APC_SERIALIZER_NAME(php);
161 void *config = NULL;
162 if (ctxt->serializer) {
163 serialize = ctxt->serializer->serialize;
164 config = ctxt->serializer->config;
165 }
166
167 if (!serialize(&buf, &buf_len, zv, config)) {
168 return 0;
169 }
170
171 /* We only ever serialize the top-level value, memoization cannot be needed */
172 ZEND_ASSERT(!ctxt->memoization_needed);
173 ctxt->serialized_str = buf;
174 ctxt->serialized_str_len = buf_len;
175
176 ADD_SIZE_STR(buf_len);
177 return 1;
178 }
179
apc_persist_calc_zval(apc_persist_context_t * ctxt,const zval * zv)180 static zend_bool apc_persist_calc_zval(apc_persist_context_t *ctxt, const zval *zv) {
181 if (Z_TYPE_P(zv) < IS_STRING) {
182 /* No data apart from the zval itself */
183 return 1;
184 }
185
186 if (ctxt->use_serialization) {
187 return apc_persist_calc_serialize(ctxt, zv);
188 }
189
190 if (apc_persist_calc_memoize(ctxt, Z_COUNTED_P(zv))) {
191 return 1;
192 }
193
194 switch (Z_TYPE_P(zv)) {
195 case IS_STRING:
196 ADD_SIZE_STR(Z_STRLEN_P(zv));
197 return 1;
198 case IS_ARRAY:
199 return apc_persist_calc_ht(ctxt, Z_ARRVAL_P(zv));
200 case IS_REFERENCE:
201 ADD_SIZE(sizeof(zend_reference));
202 return apc_persist_calc_zval(ctxt, Z_REFVAL_P(zv));
203 case IS_OBJECT:
204 ctxt->use_serialization = 1;
205 return 0;
206 case IS_RESOURCE:
207 apc_warning("Cannot store resources in apcu cache");
208 return 0;
209 EMPTY_SWITCH_DEFAULT_CASE()
210 }
211 }
212
apc_persist_calc(apc_persist_context_t * ctxt,const apc_cache_entry_t * entry)213 static zend_bool apc_persist_calc(apc_persist_context_t *ctxt, const apc_cache_entry_t *entry) {
214 ADD_SIZE(sizeof(apc_cache_entry_t));
215 ADD_SIZE_STR(ZSTR_LEN(entry->key));
216 return apc_persist_calc_zval(ctxt, &entry->val);
217 }
218
apc_persist_get_already_allocated(apc_persist_context_t * ctxt,void * ptr)219 static inline void *apc_persist_get_already_allocated(apc_persist_context_t *ctxt, void *ptr) {
220 if (ctxt->memoization_needed) {
221 return zend_hash_index_find_ptr(&ctxt->already_allocated, (uintptr_t) ptr);
222 }
223 return NULL;
224 }
225
apc_persist_add_already_allocated(apc_persist_context_t * ctxt,const void * old_ptr,void * new_ptr)226 static inline void apc_persist_add_already_allocated(
227 apc_persist_context_t *ctxt, const void *old_ptr, void *new_ptr) {
228 if (ctxt->memoization_needed) {
229 zend_hash_index_add_new_ptr(&ctxt->already_allocated, (uintptr_t) old_ptr, new_ptr);
230 }
231 }
232
apc_persist_alloc(apc_persist_context_t * ctxt,size_t size)233 static inline void *apc_persist_alloc(apc_persist_context_t *ctxt, size_t size) {
234 void *ptr = ctxt->alloc_cur;
235 ctxt->alloc_cur += ZEND_MM_ALIGNED_SIZE(size);
236 ZEND_ASSERT(ctxt->alloc_cur <= ctxt->alloc + ctxt->size);
237 return ptr;
238 }
239
apc_persist_alloc_copy(apc_persist_context_t * ctxt,const void * val,size_t size)240 static inline void *apc_persist_alloc_copy(
241 apc_persist_context_t *ctxt, const void *val, size_t size) {
242 void *ptr = apc_persist_alloc(ctxt, size);
243 memcpy(ptr, val, size);
244 return ptr;
245 }
246
apc_persist_copy_cstr(apc_persist_context_t * ctxt,const char * orig_buf,size_t buf_len,zend_ulong hash)247 static zend_string *apc_persist_copy_cstr(
248 apc_persist_context_t *ctxt, const char *orig_buf, size_t buf_len, zend_ulong hash) {
249 zend_string *str = ALLOC(_ZSTR_STRUCT_SIZE(buf_len));
250
251 GC_SET_REFCOUNT(str, 1);
252 GC_SET_PERSISTENT_TYPE(str, IS_STRING);
253
254 ZSTR_H(str) = hash;
255 ZSTR_LEN(str) = buf_len;
256 memcpy(ZSTR_VAL(str), orig_buf, buf_len);
257 ZSTR_VAL(str)[buf_len] = '\0';
258 zend_string_hash_val(str);
259
260 return str;
261 }
262
apc_persist_copy_zstr_no_add(apc_persist_context_t * ctxt,const zend_string * orig_str)263 static zend_string *apc_persist_copy_zstr_no_add(
264 apc_persist_context_t *ctxt, const zend_string *orig_str) {
265 return apc_persist_copy_cstr(
266 ctxt, ZSTR_VAL(orig_str), ZSTR_LEN(orig_str), ZSTR_H(orig_str));
267 }
268
apc_persist_copy_zstr(apc_persist_context_t * ctxt,const zend_string * orig_str)269 static inline zend_string *apc_persist_copy_zstr(
270 apc_persist_context_t *ctxt, const zend_string *orig_str) {
271 zend_string *str = apc_persist_copy_zstr_no_add(ctxt, orig_str);
272 apc_persist_add_already_allocated(ctxt, orig_str, str);
273 return str;
274 }
275
apc_persist_copy_ref(apc_persist_context_t * ctxt,const zend_reference * orig_ref)276 static zend_reference *apc_persist_copy_ref(
277 apc_persist_context_t *ctxt, const zend_reference *orig_ref) {
278 zend_reference *ref = ALLOC(sizeof(zend_reference));
279 apc_persist_add_already_allocated(ctxt, orig_ref, ref);
280
281 GC_SET_REFCOUNT(ref, 1);
282 GC_SET_PERSISTENT_TYPE(ref, GC_REFERENCE);
283 #if PHP_VERSION_ID >= 70400
284 ref->sources.ptr = NULL;
285 #endif
286
287 ZVAL_COPY_VALUE(&ref->val, &orig_ref->val);
288 apc_persist_copy_zval(ctxt, &ref->val);
289
290 return ref;
291 }
292
293 static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX};
294
apc_persist_copy_ht(apc_persist_context_t * ctxt,const HashTable * orig_ht)295 static zend_array *apc_persist_copy_ht(apc_persist_context_t *ctxt, const HashTable *orig_ht) {
296 HashTable *ht = COPY(orig_ht, sizeof(HashTable));
297 uint32_t idx;
298 apc_persist_add_already_allocated(ctxt, orig_ht, ht);
299
300 GC_SET_REFCOUNT(ht, 1);
301 GC_SET_PERSISTENT_TYPE(ht, GC_ARRAY);
302
303 /* Immutable arrays from opcache may lack a dtor and the apply protection flag. */
304 ht->pDestructor = ZVAL_PTR_DTOR;
305 #if PHP_VERSION_ID < 70300
306 ht->u.flags |= HASH_FLAG_APPLY_PROTECTION;
307 #endif
308
309 ht->u.flags |= HASH_FLAG_STATIC_KEYS;
310 if (ht->nNumUsed == 0) {
311 #if PHP_VERSION_ID >= 70400
312 ht->u.flags = HASH_FLAG_UNINITIALIZED;
313 #else
314 ht->u.flags &= ~(HASH_FLAG_INITIALIZED|HASH_FLAG_PACKED);
315 #endif
316 ht->nNextFreeElement = 0;
317 ht->nTableMask = HT_MIN_MASK;
318 HT_SET_DATA_ADDR(ht, &uninitialized_bucket);
319 return ht;
320 }
321
322 ht->nNextFreeElement = 0;
323 ht->nInternalPointer = HT_INVALID_IDX;
324 HT_SET_DATA_ADDR(ht, COPY(HT_GET_DATA_ADDR(ht), HT_USED_SIZE(ht)));
325 for (idx = 0; idx < ht->nNumUsed; idx++) {
326 Bucket *p = ht->arData + idx;
327 if (Z_TYPE(p->val) == IS_UNDEF) continue;
328
329 if (ht->nInternalPointer == HT_INVALID_IDX) {
330 ht->nInternalPointer = idx;
331 }
332
333 if (p->key) {
334 p->key = apc_persist_copy_zstr_no_add(ctxt, p->key);
335 ht->u.flags &= ~HASH_FLAG_STATIC_KEYS;
336 } else if ((zend_long) p->h >= (zend_long) ht->nNextFreeElement) {
337 ht->nNextFreeElement = p->h + 1;
338 }
339
340 apc_persist_copy_zval(ctxt, &p->val);
341 }
342
343 return ht;
344 }
345
apc_persist_copy_serialize(apc_persist_context_t * ctxt,zval * zv)346 static void apc_persist_copy_serialize(
347 apc_persist_context_t *ctxt, zval *zv) {
348 zend_string *str;
349 zend_uchar orig_type = Z_TYPE_P(zv);
350 ZEND_ASSERT(orig_type == IS_ARRAY || orig_type == IS_OBJECT);
351
352 ZEND_ASSERT(!ctxt->memoization_needed);
353 ZEND_ASSERT(ctxt->serialized_str);
354 str = apc_persist_copy_cstr(ctxt,
355 (char *) ctxt->serialized_str, ctxt->serialized_str_len, 0);
356
357 /* Store as PTR type to distinguish from other strings */
358 ZVAL_PTR(zv, str);
359 }
360
apc_persist_copy_zval_impl(apc_persist_context_t * ctxt,zval * zv)361 static void apc_persist_copy_zval_impl(apc_persist_context_t *ctxt, zval *zv) {
362 void *ptr;
363
364 if (ctxt->use_serialization) {
365 apc_persist_copy_serialize(ctxt, zv);
366 return;
367 }
368
369 ptr = apc_persist_get_already_allocated(ctxt, Z_COUNTED_P(zv));
370 switch (Z_TYPE_P(zv)) {
371 case IS_STRING:
372 if (!ptr) ptr = apc_persist_copy_zstr(ctxt, Z_STR_P(zv));
373 ZVAL_STR(zv, ptr);
374 return;
375 case IS_ARRAY:
376 if (!ptr) ptr = apc_persist_copy_ht(ctxt, Z_ARRVAL_P(zv));
377 ZVAL_ARR(zv, ptr);
378 return;
379 case IS_REFERENCE:
380 if (!ptr) ptr = apc_persist_copy_ref(ctxt, Z_REF_P(zv));
381 ZVAL_REF(zv, ptr);
382 return;
383 EMPTY_SWITCH_DEFAULT_CASE()
384 }
385 }
386
apc_persist_copy(apc_persist_context_t * ctxt,const apc_cache_entry_t * orig_entry)387 static apc_cache_entry_t *apc_persist_copy(
388 apc_persist_context_t *ctxt, const apc_cache_entry_t *orig_entry) {
389 apc_cache_entry_t *entry = COPY(orig_entry, sizeof(apc_cache_entry_t));
390 entry->key = apc_persist_copy_zstr_no_add(ctxt, entry->key);
391 apc_persist_copy_zval(ctxt, &entry->val);
392 return entry;
393 }
394
apc_persist(apc_sma_t * sma,apc_serializer_t * serializer,const apc_cache_entry_t * orig_entry)395 apc_cache_entry_t *apc_persist(
396 apc_sma_t *sma, apc_serializer_t *serializer, const apc_cache_entry_t *orig_entry) {
397 apc_persist_context_t ctxt;
398 apc_cache_entry_t *entry;
399
400 apc_persist_init_context(&ctxt, serializer);
401
402 /* The top-level value should never be a reference */
403 ZEND_ASSERT(Z_TYPE(orig_entry->val) != IS_REFERENCE);
404
405 /* If we're serializing an array using the default serializer, we will have
406 * to keep track of potentially repeated refcounted structures. */
407 if (!serializer && Z_TYPE(orig_entry->val) == IS_ARRAY) {
408 ctxt.memoization_needed = 1;
409 zend_hash_init(&ctxt.already_counted, 0, NULL, NULL, 0);
410 zend_hash_init(&ctxt.already_allocated, 0, NULL, NULL, 0);
411 }
412
413 /* Objects are always serialized, and arrays when a serializer is set.
414 * Other cases are detected during apc_persist_calc(). */
415 if (Z_TYPE(orig_entry->val) == IS_OBJECT
416 || (serializer && Z_TYPE(orig_entry->val) == IS_ARRAY)) {
417 ctxt.use_serialization = 1;
418 }
419
420 if (!apc_persist_calc(&ctxt, orig_entry)) {
421 if (!ctxt.use_serialization) {
422 apc_persist_destroy_context(&ctxt);
423 return NULL;
424 }
425
426 /* Try again with serialization */
427 apc_persist_destroy_context(&ctxt);
428 apc_persist_init_context(&ctxt, serializer);
429 ctxt.use_serialization = 1;
430 if (!apc_persist_calc(&ctxt, orig_entry)) {
431 apc_persist_destroy_context(&ctxt);
432 return NULL;
433 }
434 }
435
436 ctxt.alloc = ctxt.alloc_cur = apc_sma_malloc(sma, ctxt.size);
437 if (!ctxt.alloc) {
438 apc_persist_destroy_context(&ctxt);
439 return NULL;
440 }
441
442 entry = apc_persist_copy(&ctxt, orig_entry);
443 ZEND_ASSERT(ctxt.alloc_cur == ctxt.alloc + ctxt.size);
444
445 entry->mem_size = ctxt.size;
446
447 apc_persist_destroy_context(&ctxt);
448 return entry;
449 }
450
451 /*
452 * UNPERSIST: Copy from SHM to request memory.
453 */
454
455 typedef struct _apc_unpersist_context_t {
456 /* Whether we need to memoize already copied refcounteds. */
457 zend_bool memoization_needed;
458 /* HashTable storing already copied refcounteds. */
459 HashTable already_copied;
460 } apc_unpersist_context_t;
461
462 static void apc_unpersist_zval_impl(apc_unpersist_context_t *ctxt, zval *zv);
463
apc_unpersist_zval(apc_unpersist_context_t * ctxt,zval * zv)464 static inline void apc_unpersist_zval(apc_unpersist_context_t *ctxt, zval *zv) {
465 /* No data apart from the zval itself */
466 if (Z_TYPE_P(zv) < IS_STRING) {
467 return;
468 }
469
470 apc_unpersist_zval_impl(ctxt, zv);
471 }
472
apc_unpersist_serialized(zval * dst,zend_string * str,apc_serializer_t * serializer)473 static zend_bool apc_unpersist_serialized(
474 zval *dst, zend_string *str, apc_serializer_t *serializer) {
475 apc_unserialize_t unserialize = APC_UNSERIALIZER_NAME(php);
476 void *config = NULL;
477
478 if (serializer) {
479 unserialize = serializer->unserialize;
480 config = serializer->config;
481 }
482
483 if (unserialize(dst, (unsigned char *) ZSTR_VAL(str), ZSTR_LEN(str), config)) {
484 return 1;
485 }
486
487 ZVAL_NULL(dst);
488 return 0;
489 }
490
apc_unpersist_get_already_copied(apc_unpersist_context_t * ctxt,void * ptr)491 static inline void *apc_unpersist_get_already_copied(apc_unpersist_context_t *ctxt, void *ptr) {
492 if (ctxt->memoization_needed) {
493 return zend_hash_index_find_ptr(&ctxt->already_copied, (uintptr_t) ptr);
494 }
495 return NULL;
496 }
497
apc_unpersist_add_already_copied(apc_unpersist_context_t * ctxt,const void * old_ptr,void * new_ptr)498 static inline void apc_unpersist_add_already_copied(
499 apc_unpersist_context_t *ctxt, const void *old_ptr, void *new_ptr) {
500 if (ctxt->memoization_needed) {
501 zend_hash_index_add_new_ptr(&ctxt->already_copied, (uintptr_t) old_ptr, new_ptr);
502 }
503 }
504
apc_unpersist_zstr(apc_unpersist_context_t * ctxt,const zend_string * orig_str)505 static zend_string *apc_unpersist_zstr(apc_unpersist_context_t *ctxt, const zend_string *orig_str) {
506 zend_string *str = zend_string_init(ZSTR_VAL(orig_str), ZSTR_LEN(orig_str), 0);
507 ZSTR_H(str) = ZSTR_H(orig_str);
508 apc_unpersist_add_already_copied(ctxt, orig_str, str);
509 return str;
510 }
511
apc_unpersist_ref(apc_unpersist_context_t * ctxt,const zend_reference * orig_ref)512 static zend_reference *apc_unpersist_ref(
513 apc_unpersist_context_t *ctxt, const zend_reference *orig_ref) {
514 zend_reference *ref = emalloc(sizeof(zend_reference));
515 apc_unpersist_add_already_copied(ctxt, orig_ref, ref);
516
517 GC_SET_REFCOUNT(ref, 1);
518 GC_TYPE_INFO(ref) = GC_REFERENCE;
519 #if PHP_VERSION_ID >= 70400
520 ref->sources.ptr = NULL;
521 #endif
522
523 ZVAL_COPY_VALUE(&ref->val, &orig_ref->val);
524 apc_unpersist_zval(ctxt, &ref->val);
525 return ref;
526 }
527
apc_unpersist_ht(apc_unpersist_context_t * ctxt,const HashTable * orig_ht)528 static zend_array *apc_unpersist_ht(
529 apc_unpersist_context_t *ctxt, const HashTable *orig_ht) {
530 HashTable *ht = emalloc(sizeof(HashTable));
531
532 apc_unpersist_add_already_copied(ctxt, orig_ht, ht);
533 memcpy(ht, orig_ht, sizeof(HashTable));
534 GC_TYPE_INFO(ht) = GC_ARRAY;
535
536 if (ht->nNumUsed == 0) {
537 HT_SET_DATA_ADDR(ht, &uninitialized_bucket);
538 return ht;
539 }
540
541 HT_SET_DATA_ADDR(ht, emalloc(HT_SIZE(ht)));
542 memcpy(HT_GET_DATA_ADDR(ht), HT_GET_DATA_ADDR(orig_ht), HT_HASH_SIZE(ht->nTableMask));
543
544 if (ht->u.flags & HASH_FLAG_STATIC_KEYS) {
545 Bucket *p = ht->arData, *q = orig_ht->arData, *p_end = p + ht->nNumUsed;
546 for (; p < p_end; p++, q++) {
547 /* No need to check for UNDEF, as unpersist_zval can be safely called on UNDEF */
548 *p = *q;
549 apc_unpersist_zval(ctxt, &p->val);
550 }
551 } else {
552 Bucket *p = ht->arData, *q = orig_ht->arData, *p_end = p + ht->nNumUsed;
553 for (; p < p_end; p++, q++) {
554 if (Z_TYPE(q->val) == IS_UNDEF) {
555 ZVAL_UNDEF(&p->val);
556 continue;
557 }
558
559 p->val = q->val;
560 p->h = q->h;
561 if (q->key) {
562 p->key = zend_string_dup(q->key, 0);
563 } else {
564 p->key = NULL;
565 }
566 apc_unpersist_zval(ctxt, &p->val);
567 }
568 }
569
570 return ht;
571 }
572
apc_unpersist_zval_impl(apc_unpersist_context_t * ctxt,zval * zv)573 static void apc_unpersist_zval_impl(apc_unpersist_context_t *ctxt, zval *zv) {
574 void *ptr = apc_unpersist_get_already_copied(ctxt, Z_COUNTED_P(zv));
575 if (ptr) {
576 Z_COUNTED_P(zv) = ptr;
577 Z_ADDREF_P(zv);
578 return;
579 }
580
581 switch (Z_TYPE_P(zv)) {
582 case IS_STRING:
583 Z_STR_P(zv) = apc_unpersist_zstr(ctxt, Z_STR_P(zv));
584 return;
585 case IS_REFERENCE:
586 Z_REF_P(zv) = apc_unpersist_ref(ctxt, Z_REF_P(zv));
587 return;
588 case IS_ARRAY:
589 Z_ARR_P(zv) = apc_unpersist_ht(ctxt, Z_ARR_P(zv));
590 return;
591 default:
592 ZEND_ASSERT(0);
593 return;
594 }
595 }
596
apc_unpersist(zval * dst,const zval * value,apc_serializer_t * serializer)597 zend_bool apc_unpersist(zval *dst, const zval *value, apc_serializer_t *serializer) {
598 apc_unpersist_context_t ctxt;
599
600 if (Z_TYPE_P(value) == IS_PTR) {
601 return apc_unpersist_serialized(dst, Z_PTR_P(value), serializer);
602 }
603
604 ctxt.memoization_needed = 0;
605 ZEND_ASSERT(Z_TYPE_P(value) != IS_REFERENCE);
606 if (Z_TYPE_P(value) == IS_ARRAY) {
607 ctxt.memoization_needed = 1;
608 zend_hash_init(&ctxt.already_copied, 0, NULL, NULL, 0);
609 }
610
611 ZVAL_COPY_VALUE(dst, value);
612 apc_unpersist_zval(&ctxt, dst);
613
614 if (ctxt.memoization_needed) {
615 zend_hash_destroy(&ctxt.already_copied);
616 }
617 return 1;
618 }
619