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