xref: /PHP-5.5/Zend/zend_gc.c (revision f29c98c1)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine                                                          |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1998-2015 Zend Technologies Ltd. (http://www.zend.com) |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt.                                |
11    | If you did not receive a copy of the Zend license and are unable to  |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@zend.com so we can mail you a copy immediately.              |
14    +----------------------------------------------------------------------+
15    | Authors: David Wang <planetbeing@gmail.com>                          |
16    |          Dmitry Stogov <dmitry@zend.com>                             |
17    +----------------------------------------------------------------------+
18 */
19 
20 /* $Id$ */
21 
22 #include "zend.h"
23 #include "zend_API.h"
24 
25 #define GC_ROOT_BUFFER_MAX_ENTRIES 10000
26 
27 #ifdef ZTS
28 ZEND_API int gc_globals_id;
29 #else
30 ZEND_API zend_gc_globals gc_globals;
31 #endif
32 
root_buffer_dtor(zend_gc_globals * gc_globals TSRMLS_DC)33 static void root_buffer_dtor(zend_gc_globals *gc_globals TSRMLS_DC)
34 {
35 	if (gc_globals->buf) {
36 		free(gc_globals->buf);
37 		gc_globals->buf = NULL;
38 	}
39 }
40 
gc_globals_ctor_ex(zend_gc_globals * gc_globals TSRMLS_DC)41 static void gc_globals_ctor_ex(zend_gc_globals *gc_globals TSRMLS_DC)
42 {
43 	gc_globals->gc_enabled = 0;
44 	gc_globals->gc_active = 0;
45 
46 	gc_globals->buf = NULL;
47 
48 	gc_globals->roots.next = &gc_globals->roots;
49 	gc_globals->roots.prev = &gc_globals->roots;
50 	gc_globals->unused = NULL;
51 	gc_globals->zval_to_free = NULL;
52 	gc_globals->free_list = NULL;
53 	gc_globals->next_to_free = NULL;
54 
55 	gc_globals->gc_runs = 0;
56 	gc_globals->collected = 0;
57 
58 #if GC_BENCH
59 	gc_globals->root_buf_length = 0;
60 	gc_globals->root_buf_peak = 0;
61 	gc_globals->zval_possible_root = 0;
62 	gc_globals->zobj_possible_root = 0;
63 	gc_globals->zval_buffered = 0;
64 	gc_globals->zobj_buffered = 0;
65 	gc_globals->zval_remove_from_buffer = 0;
66 	gc_globals->zobj_remove_from_buffer = 0;
67 	gc_globals->zval_marked_grey = 0;
68 	gc_globals->zobj_marked_grey = 0;
69 #endif
70 }
71 
gc_globals_ctor(TSRMLS_D)72 ZEND_API void gc_globals_ctor(TSRMLS_D)
73 {
74 #ifdef ZTS
75 	ts_allocate_id(&gc_globals_id, sizeof(zend_gc_globals), (ts_allocate_ctor) gc_globals_ctor_ex, (ts_allocate_dtor) root_buffer_dtor);
76 #else
77 	gc_globals_ctor_ex(&gc_globals);
78 #endif
79 }
80 
gc_globals_dtor(TSRMLS_D)81 ZEND_API void gc_globals_dtor(TSRMLS_D)
82 {
83 #ifndef ZTS
84 	root_buffer_dtor(&gc_globals TSRMLS_DC);
85 #endif
86 }
87 
gc_reset(TSRMLS_D)88 ZEND_API void gc_reset(TSRMLS_D)
89 {
90 	GC_G(gc_runs) = 0;
91 	GC_G(collected) = 0;
92 
93 #if GC_BENCH
94 	GC_G(root_buf_length) = 0;
95 	GC_G(root_buf_peak) = 0;
96 	GC_G(zval_possible_root) = 0;
97 	GC_G(zobj_possible_root) = 0;
98 	GC_G(zval_buffered) = 0;
99 	GC_G(zobj_buffered) = 0;
100 	GC_G(zval_remove_from_buffer) = 0;
101 	GC_G(zobj_remove_from_buffer) = 0;
102 	GC_G(zval_marked_grey) = 0;
103 	GC_G(zobj_marked_grey) = 0;
104 #endif
105 
106 	GC_G(roots).next = &GC_G(roots);
107 	GC_G(roots).prev = &GC_G(roots);
108 
109 	if (GC_G(buf)) {
110 		GC_G(unused) = NULL;
111 		GC_G(first_unused) = GC_G(buf);
112 
113 		GC_G(zval_to_free) = NULL;
114 	} else {
115 		GC_G(unused) = NULL;
116 		GC_G(first_unused) = NULL;
117 		GC_G(last_unused) = NULL;
118 	}
119 }
120 
gc_init(TSRMLS_D)121 ZEND_API void gc_init(TSRMLS_D)
122 {
123 	if (GC_G(buf) == NULL && GC_G(gc_enabled)) {
124 		GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);
125 		GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
126 		gc_reset(TSRMLS_C);
127 	}
128 }
129 
gc_zval_possible_root(zval * zv TSRMLS_DC)130 ZEND_API void gc_zval_possible_root(zval *zv TSRMLS_DC)
131 {
132 	if (UNEXPECTED(GC_G(free_list) != NULL &&
133 	               GC_ZVAL_ADDRESS(zv) != NULL &&
134 		           GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
135 		           (GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
136 		            GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused))) {
137 		/* The given zval is a garbage that is going to be deleted by
138 		 * currently running GC */
139 		return;
140 	}
141 
142 	if (zv->type == IS_OBJECT) {
143 		GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
144 		return;
145 	}
146 
147 	GC_BENCH_INC(zval_possible_root);
148 
149 	if (GC_ZVAL_GET_COLOR(zv) != GC_PURPLE) {
150 		if (!GC_ZVAL_ADDRESS(zv)) {
151 			gc_root_buffer *newRoot = GC_G(unused);
152 
153 			if (newRoot) {
154 				GC_G(unused) = newRoot->prev;
155 			} else if (GC_G(first_unused) != GC_G(last_unused)) {
156 				newRoot = GC_G(first_unused);
157 				GC_G(first_unused)++;
158 			} else {
159 				if (!GC_G(gc_enabled)) {
160 					return;
161 				}
162 				zv->refcount__gc++;
163 				gc_collect_cycles(TSRMLS_C);
164 				zv->refcount__gc--;
165 				newRoot = GC_G(unused);
166 				if (!newRoot) {
167 					return;
168 				}
169 				GC_G(unused) = newRoot->prev;
170 			}
171 
172 			GC_ZVAL_SET_PURPLE(zv);
173 			newRoot->next = GC_G(roots).next;
174 			newRoot->prev = &GC_G(roots);
175 			GC_G(roots).next->prev = newRoot;
176 			GC_G(roots).next = newRoot;
177 
178 			GC_ZVAL_SET_ADDRESS(zv, newRoot);
179 
180 			newRoot->handle = 0;
181 			newRoot->u.pz = zv;
182 
183 			GC_BENCH_INC(zval_buffered);
184 			GC_BENCH_INC(root_buf_length);
185 			GC_BENCH_PEAK(root_buf_peak, root_buf_length);
186 		}
187 	}
188 }
189 
gc_zobj_possible_root(zval * zv TSRMLS_DC)190 ZEND_API void gc_zobj_possible_root(zval *zv TSRMLS_DC)
191 {
192 	struct _store_object *obj;
193 
194 	if (UNEXPECTED(Z_OBJ_HT_P(zv)->get_gc == NULL ||
195 	    EG(objects_store).object_buckets == NULL)) {
196 		return;
197 	}
198 
199 	GC_BENCH_INC(zobj_possible_root);
200 
201 	obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(zv)].bucket.obj;
202 	if (GC_GET_COLOR(obj->buffered) != GC_PURPLE) {
203 		if (!GC_ADDRESS(obj->buffered)) {
204 			gc_root_buffer *newRoot = GC_G(unused);
205 
206 			if (newRoot) {
207 				GC_G(unused) = newRoot->prev;
208 			} else if (GC_G(first_unused) != GC_G(last_unused)) {
209 				newRoot = GC_G(first_unused);
210 				GC_G(first_unused)++;
211 			} else {
212 				if (!GC_G(gc_enabled)) {
213 					return;
214 				}
215 				zv->refcount__gc++;
216 				gc_collect_cycles(TSRMLS_C);
217 				zv->refcount__gc--;
218 				newRoot = GC_G(unused);
219 				if (!newRoot) {
220 					return;
221 				}
222 				obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(zv)].bucket.obj;
223 				GC_G(unused) = newRoot->prev;
224 			}
225 
226 			GC_SET_PURPLE(obj->buffered);
227 			newRoot->next = GC_G(roots).next;
228 			newRoot->prev = &GC_G(roots);
229 			GC_G(roots).next->prev = newRoot;
230 			GC_G(roots).next = newRoot;
231 
232 			GC_SET_ADDRESS(obj->buffered, newRoot);
233 
234 			newRoot->handle = Z_OBJ_HANDLE_P(zv);
235 			newRoot->u.handlers = Z_OBJ_HT_P(zv);
236 
237 			GC_BENCH_INC(zobj_buffered);
238 			GC_BENCH_INC(root_buf_length);
239 			GC_BENCH_PEAK(root_buf_peak, root_buf_length);
240 		}
241 	}
242 }
243 
gc_remove_zval_from_buffer(zval * zv TSRMLS_DC)244 ZEND_API void gc_remove_zval_from_buffer(zval *zv TSRMLS_DC)
245 {
246 	gc_root_buffer* root_buffer = GC_ADDRESS(((zval_gc_info*)zv)->u.buffered);
247 
248 	if (UNEXPECTED(GC_G(free_list) != NULL &&
249 		           GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
250 		           (GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
251 		            GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused))) {
252 		/* The given zval is a garbage that is going to be deleted by
253 		 * currently running GC */
254 		if (GC_G(next_to_free) == (zval_gc_info*)zv) {
255 			GC_G(next_to_free) = ((zval_gc_info*)zv)->u.next;
256 		}
257 		return;
258 	}
259 	GC_BENCH_INC(zval_remove_from_buffer);
260 	GC_REMOVE_FROM_BUFFER(root_buffer);
261 	((zval_gc_info*)zv)->u.buffered = NULL;
262 }
263 
zval_scan_black(zval * pz TSRMLS_DC)264 static void zval_scan_black(zval *pz TSRMLS_DC)
265 {
266 	Bucket *p;
267 
268 tail_call:
269 	p = NULL;
270 	GC_ZVAL_SET_BLACK(pz);
271 
272 	if (Z_TYPE_P(pz) == IS_OBJECT && EG(objects_store).object_buckets) {
273 		zend_object_get_gc_t get_gc;
274 		struct _store_object *obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].bucket.obj;
275 
276 		obj->refcount++;
277 		if (GC_GET_COLOR(obj->buffered) != GC_BLACK) {
278 			GC_SET_BLACK(obj->buffered);
279 			if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
280 			             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
281 				int i, n;
282 				zval **table;
283 				HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
284 
285 				while (n > 0 && !table[n-1]) n--;
286 				for (i = 0; i < n; i++) {
287 					if (table[i]) {
288 						pz = table[i];
289 						if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
290 							pz->refcount__gc++;
291 						}
292 						if (GC_ZVAL_GET_COLOR(pz) != GC_BLACK) {
293 							if (!props && i == n - 1) {
294 								goto tail_call;
295 							} else {
296 								zval_scan_black(pz TSRMLS_CC);
297 							}
298 						}
299 					}
300 				}
301 				if (!props) {
302 					return;
303 				}
304 				p = props->pListHead;
305 			}
306 		}
307 	} else if (Z_TYPE_P(pz) == IS_ARRAY) {
308 		if (Z_ARRVAL_P(pz) != &EG(symbol_table)) {
309 			p = Z_ARRVAL_P(pz)->pListHead;
310 		}
311 	}
312 	while (p != NULL) {
313 		pz = *(zval**)p->pData;
314 		if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
315 			pz->refcount__gc++;
316 		}
317 		if (GC_ZVAL_GET_COLOR(pz) != GC_BLACK) {
318 			if (p->pListNext == NULL) {
319 				goto tail_call;
320 			} else {
321 				zval_scan_black(pz TSRMLS_CC);
322 			}
323 		}
324 		p = p->pListNext;
325 	}
326 }
327 
zobj_scan_black(struct _store_object * obj,zval * pz TSRMLS_DC)328 static void zobj_scan_black(struct _store_object *obj, zval *pz TSRMLS_DC)
329 {
330 	Bucket *p;
331 	zend_object_get_gc_t get_gc;
332 
333 	GC_SET_BLACK(obj->buffered);
334 	if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
335 	             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
336 		int i, n;
337 		zval **table;
338 		HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
339 
340 		for (i = 0; i < n; i++) {
341 			if (table[i]) {
342 				pz = table[i];
343 				if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
344 					pz->refcount__gc++;
345 				}
346 				if (GC_ZVAL_GET_COLOR(pz) != GC_BLACK) {
347 					zval_scan_black(pz TSRMLS_CC);
348 				}
349 			}
350 		}
351 		if (!props) {
352 			return;
353 		}
354 		p = props->pListHead;
355 		while (p != NULL) {
356 			pz = *(zval**)p->pData;
357 			if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
358 				pz->refcount__gc++;
359 			}
360 			if (GC_ZVAL_GET_COLOR(pz) != GC_BLACK) {
361 				zval_scan_black(pz TSRMLS_CC);
362 			}
363 			p = p->pListNext;
364 		}
365 	}
366 }
367 
zval_mark_grey(zval * pz TSRMLS_DC)368 static void zval_mark_grey(zval *pz TSRMLS_DC)
369 {
370 	Bucket *p;
371 
372 tail_call:
373 	if (GC_ZVAL_GET_COLOR(pz) != GC_GREY) {
374 		p = NULL;
375 		GC_BENCH_INC(zval_marked_grey);
376 		GC_ZVAL_SET_COLOR(pz, GC_GREY);
377 
378 		if (Z_TYPE_P(pz) == IS_OBJECT && EG(objects_store).object_buckets) {
379 			zend_object_get_gc_t get_gc;
380 			struct _store_object *obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].bucket.obj;
381 
382 			obj->refcount--;
383 			if (GC_GET_COLOR(obj->buffered) != GC_GREY) {
384 				GC_BENCH_INC(zobj_marked_grey);
385 				GC_SET_COLOR(obj->buffered, GC_GREY);
386 				if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
387 				             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
388 					int i, n;
389 					zval **table;
390 					HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
391 
392 					while (n > 0 && !table[n-1]) n--;
393 					for (i = 0; i < n; i++) {
394 						if (table[i]) {
395 							pz = table[i];
396 							if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
397 								pz->refcount__gc--;
398 							}
399 							if (!props && i == n - 1) {
400 								goto tail_call;
401 							} else {
402 								zval_mark_grey(pz TSRMLS_CC);
403 							}
404 						}
405 					}
406 					if (!props) {
407 						return;
408 					}
409 					p = props->pListHead;
410 				}
411 			}
412 		} else if (Z_TYPE_P(pz) == IS_ARRAY) {
413 			if (Z_ARRVAL_P(pz) == &EG(symbol_table)) {
414 				GC_ZVAL_SET_BLACK(pz);
415 			} else {
416 				p = Z_ARRVAL_P(pz)->pListHead;
417 			}
418 		}
419 		while (p != NULL) {
420 			pz = *(zval**)p->pData;
421 			if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
422 				pz->refcount__gc--;
423 			}
424 			if (p->pListNext == NULL) {
425 				goto tail_call;
426 			} else {
427 				zval_mark_grey(pz TSRMLS_CC);
428 			}
429 			p = p->pListNext;
430 		}
431 	}
432 }
433 
zobj_mark_grey(struct _store_object * obj,zval * pz TSRMLS_DC)434 static void zobj_mark_grey(struct _store_object *obj, zval *pz TSRMLS_DC)
435 {
436 	Bucket *p;
437 	zend_object_get_gc_t get_gc;
438 
439 	if (GC_GET_COLOR(obj->buffered) != GC_GREY) {
440 		GC_BENCH_INC(zobj_marked_grey);
441 		GC_SET_COLOR(obj->buffered, GC_GREY);
442 		if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
443 		             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
444 			int i, n;
445 			zval **table;
446 			HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
447 
448 			for (i = 0; i < n; i++) {
449 				if (table[i]) {
450 					pz = table[i];
451 					if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
452 						pz->refcount__gc--;
453 					}
454 					zval_mark_grey(pz TSRMLS_CC);
455 				}
456 			}
457 			if (!props) {
458 				return;
459 			}
460 			p = props->pListHead;
461 			while (p != NULL) {
462 				pz = *(zval**)p->pData;
463 				if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
464 					pz->refcount__gc--;
465 				}
466 				zval_mark_grey(pz TSRMLS_CC);
467 				p = p->pListNext;
468 			}
469 		}
470 	}
471 }
472 
gc_mark_roots(TSRMLS_D)473 static void gc_mark_roots(TSRMLS_D)
474 {
475 	gc_root_buffer *current = GC_G(roots).next;
476 
477 	while (current != &GC_G(roots)) {
478 		if (current->handle) {
479 			if (EG(objects_store).object_buckets) {
480 				struct _store_object *obj = &EG(objects_store).object_buckets[current->handle].bucket.obj;
481 
482 				if (GC_GET_COLOR(obj->buffered) == GC_PURPLE) {
483 					zval z;
484 
485 					INIT_PZVAL(&z);
486 					Z_OBJ_HANDLE(z) = current->handle;
487 					Z_OBJ_HT(z) = current->u.handlers;
488 					zobj_mark_grey(obj, &z TSRMLS_CC);
489 				} else {
490 					GC_SET_ADDRESS(obj->buffered, NULL);
491 					GC_REMOVE_FROM_BUFFER(current);
492 				}
493 			}
494 		} else {
495 			if (GC_ZVAL_GET_COLOR(current->u.pz) == GC_PURPLE) {
496 				zval_mark_grey(current->u.pz TSRMLS_CC);
497 			} else {
498 				GC_ZVAL_SET_ADDRESS(current->u.pz, NULL);
499 				GC_REMOVE_FROM_BUFFER(current);
500 			}
501 		}
502 		current = current->next;
503 	}
504 }
505 
zval_scan(zval * pz TSRMLS_DC)506 static void zval_scan(zval *pz TSRMLS_DC)
507 {
508 	Bucket *p;
509 
510 tail_call:
511 	if (GC_ZVAL_GET_COLOR(pz) == GC_GREY) {
512 		p = NULL;
513 		if (pz->refcount__gc > 0) {
514 			zval_scan_black(pz TSRMLS_CC);
515 		} else {
516 			GC_ZVAL_SET_COLOR(pz, GC_WHITE);
517 			if (Z_TYPE_P(pz) == IS_OBJECT && EG(objects_store).object_buckets) {
518 				zend_object_get_gc_t get_gc;
519 				struct _store_object *obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].bucket.obj;
520 
521 				if (GC_GET_COLOR(obj->buffered) == GC_GREY) {
522 					if (obj->refcount > 0) {
523 						zobj_scan_black(obj, pz TSRMLS_CC);
524 					} else {
525 						GC_SET_COLOR(obj->buffered, GC_WHITE);
526 						if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
527 						             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
528 							int i, n;
529 							zval **table;
530 							HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
531 
532 							while (n > 0 && !table[n-1]) n--;
533 							for (i = 0; i < n; i++) {
534 								if (table[i]) {
535 									pz = table[i];
536 									if (!props && i == n - 1) {
537 										goto tail_call;
538 									} else {
539 										zval_scan(pz TSRMLS_CC);
540 									}
541 								}
542 							}
543 							if (!props) {
544 								return;
545 							}
546 							p = props->pListHead;
547 						}
548 					}
549 				}
550 			} else if (Z_TYPE_P(pz) == IS_ARRAY) {
551 				if (Z_ARRVAL_P(pz) == &EG(symbol_table)) {
552 					GC_ZVAL_SET_BLACK(pz);
553 				} else {
554 					p = Z_ARRVAL_P(pz)->pListHead;
555 				}
556 			}
557 		}
558 		while (p != NULL) {
559 			if (p->pListNext == NULL) {
560 				pz = *(zval**)p->pData;
561 				goto tail_call;
562 			} else {
563 				zval_scan(*(zval**)p->pData TSRMLS_CC);
564 			}
565 			p = p->pListNext;
566 		}
567 	}
568 }
569 
zobj_scan(zval * pz TSRMLS_DC)570 static void zobj_scan(zval *pz TSRMLS_DC)
571 {
572 	Bucket *p;
573 	zend_object_get_gc_t get_gc;
574 
575 	if (EG(objects_store).object_buckets) {
576 		struct _store_object *obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].bucket.obj;
577 
578 		if (GC_GET_COLOR(obj->buffered) == GC_GREY) {
579 			if (obj->refcount > 0) {
580 				zobj_scan_black(obj, pz TSRMLS_CC);
581 			} else {
582 				GC_SET_COLOR(obj->buffered, GC_WHITE);
583 				if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
584 				             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
585 					int i, n;
586 					zval **table;
587 					HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
588 
589 					for (i = 0; i < n; i++) {
590 						if (table[i]) {
591 							pz = table[i];
592 							zval_scan(pz TSRMLS_CC);
593                     	}
594 					}
595 					if (!props) {
596 						return;
597 					}
598 					p = props->pListHead;
599 					while (p != NULL) {
600 						zval_scan(*(zval**)p->pData TSRMLS_CC);
601 						p = p->pListNext;
602 					}
603 				}
604 			}
605 		}
606 	}
607 }
608 
gc_scan_roots(TSRMLS_D)609 static void gc_scan_roots(TSRMLS_D)
610 {
611 	gc_root_buffer *current = GC_G(roots).next;
612 
613 	while (current != &GC_G(roots)) {
614 		if (current->handle) {
615 			zval z;
616 
617 			INIT_PZVAL(&z);
618 			Z_OBJ_HANDLE(z) = current->handle;
619 			Z_OBJ_HT(z) = current->u.handlers;
620 			zobj_scan(&z TSRMLS_CC);
621 		} else {
622 			zval_scan(current->u.pz TSRMLS_CC);
623 		}
624 		current = current->next;
625 	}
626 }
627 
zval_collect_white(zval * pz TSRMLS_DC)628 static void zval_collect_white(zval *pz TSRMLS_DC)
629 {
630 	Bucket *p;
631 
632 tail_call:
633 	if (((zval_gc_info*)(pz))->u.buffered == (gc_root_buffer*)GC_WHITE) {
634 		p = NULL;
635 		GC_ZVAL_SET_BLACK(pz);
636 
637 		if (Z_TYPE_P(pz) == IS_OBJECT && EG(objects_store).object_buckets) {
638 			zend_object_get_gc_t get_gc;
639 			struct _store_object *obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].bucket.obj;
640 
641 			if (obj->buffered == (gc_root_buffer*)GC_WHITE) {
642 				/* PURPLE instead of BLACK to prevent buffering in nested gc calls */
643 				GC_SET_PURPLE(obj->buffered);
644 
645 				if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
646 				             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
647 					int i, n;
648 					zval **table, *zv;
649 					HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
650 
651 					if (!props) {
652 						/* restore refcount and put into list to free */
653 						pz->refcount__gc++;
654 						((zval_gc_info*)pz)->u.next = GC_G(zval_to_free);
655 						GC_G(zval_to_free) = (zval_gc_info*)pz;
656 					}
657 
658 					while (n > 0 && !table[n-1]) n--;
659 					for (i = 0; i < n; i++) {
660 						if (table[i]) {
661 							zv = table[i];
662 							if (Z_TYPE_P(zv) != IS_ARRAY || Z_ARRVAL_P(zv) != &EG(symbol_table)) {
663 								zv->refcount__gc++;
664 							}
665 							if (!props && i == n - 1) {
666 								pz = zv;
667 								goto tail_call;
668 							} else {
669 								zval_collect_white(zv TSRMLS_CC);
670 							}
671 						}
672 					}
673 					if (!props) {
674 						return;
675 					}
676 					p = props->pListHead;
677 				}
678 			}
679 		} else {
680 			if (Z_TYPE_P(pz) == IS_ARRAY) {
681 				p = Z_ARRVAL_P(pz)->pListHead;
682 			}
683 		}
684 
685 		/* restore refcount and put into list to free */
686 		pz->refcount__gc++;
687 		((zval_gc_info*)pz)->u.next = GC_G(zval_to_free);
688 		GC_G(zval_to_free) = (zval_gc_info*)pz;
689 
690 		while (p != NULL) {
691 			pz = *(zval**)p->pData;
692 			if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
693 				pz->refcount__gc++;
694 			}
695 			if (p->pListNext == NULL) {
696 				goto tail_call;
697 			} else {
698 				zval_collect_white(pz TSRMLS_CC);
699 			}
700 			p = p->pListNext;
701 		}
702 	}
703 }
704 
zobj_collect_white(zval * pz TSRMLS_DC)705 static void zobj_collect_white(zval *pz TSRMLS_DC)
706 {
707 	Bucket *p;
708 
709 	if (EG(objects_store).object_buckets) {
710 		zend_object_get_gc_t get_gc;
711 		struct _store_object *obj = &EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].bucket.obj;
712 
713 		if (obj->buffered == (gc_root_buffer*)GC_WHITE) {
714 			/* PURPLE instead of BLACK to prevent buffering in nested gc calls */
715 			GC_SET_PURPLE(obj->buffered);
716 
717 			if (EXPECTED(EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(pz)].valid &&
718 			             (get_gc = Z_OBJ_HANDLER_P(pz, get_gc)) != NULL)) {
719 				int i, n;
720 				zval **table;
721 				HashTable *props = get_gc(pz, &table, &n TSRMLS_CC);
722 
723 				for (i = 0; i < n; i++) {
724 					if (table[i]) {
725 						pz = table[i];
726 						if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
727 							pz->refcount__gc++;
728 						}
729 						zval_collect_white(pz TSRMLS_CC);
730 					}
731 				}
732 				if (!props) {
733 					return;
734 				}
735 				p = props->pListHead;
736 				while (p != NULL) {
737 					pz = *(zval**)p->pData;
738 					if (Z_TYPE_P(pz) != IS_ARRAY || Z_ARRVAL_P(pz) != &EG(symbol_table)) {
739 						pz->refcount__gc++;
740 					}
741 					zval_collect_white(pz TSRMLS_CC);
742 					p = p->pListNext;
743 				}
744 			}
745 		}
746 	}
747 }
748 
gc_collect_roots(TSRMLS_D)749 static void gc_collect_roots(TSRMLS_D)
750 {
751 	gc_root_buffer *current = GC_G(roots).next;
752 
753 	while (current != &GC_G(roots)) {
754 		if (current->handle) {
755 			if (EG(objects_store).object_buckets) {
756 				struct _store_object *obj = &EG(objects_store).object_buckets[current->handle].bucket.obj;
757 				zval z;
758 
759 				GC_SET_ADDRESS(obj->buffered, NULL);
760 				INIT_PZVAL(&z);
761 				Z_OBJ_HANDLE(z) = current->handle;
762 				Z_OBJ_HT(z) = current->u.handlers;
763 				zobj_collect_white(&z TSRMLS_CC);
764 			}
765 		} else {
766 			GC_ZVAL_SET_ADDRESS(current->u.pz, NULL);
767 			zval_collect_white(current->u.pz TSRMLS_CC);
768 		}
769 
770 		GC_REMOVE_FROM_BUFFER(current);
771 		current = current->next;
772 	}
773 }
774 
775 #define FREE_LIST_END ((zval_gc_info*)(~(zend_uintptr_t)GC_COLOR))
776 
gc_collect_cycles(TSRMLS_D)777 ZEND_API int gc_collect_cycles(TSRMLS_D)
778 {
779 	int count = 0;
780 
781 	if (GC_G(roots).next != &GC_G(roots)) {
782 		zval_gc_info *p, *q, *orig_free_list, *orig_next_to_free;
783 
784 		if (GC_G(gc_active)) {
785 			return 0;
786 		}
787 		GC_G(gc_runs)++;
788 		GC_G(zval_to_free) = FREE_LIST_END;
789 		GC_G(gc_active) = 1;
790 		gc_mark_roots(TSRMLS_C);
791 		gc_scan_roots(TSRMLS_C);
792 		gc_collect_roots(TSRMLS_C);
793 
794 		orig_free_list = GC_G(free_list);
795 		orig_next_to_free = GC_G(next_to_free);
796 		p = GC_G(free_list) = GC_G(zval_to_free);
797 		GC_G(zval_to_free) = NULL;
798 		GC_G(gc_active) = 0;
799 
800 		/* First call destructors */
801 		while (p != FREE_LIST_END) {
802 			if (Z_TYPE(p->z) == IS_OBJECT) {
803 				if (EG(objects_store).object_buckets &&
804 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
805 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0 &&
806 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor &&
807 					!EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called) {
808 
809 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called = 1;
810 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount++;
811 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor(EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.object, Z_OBJ_HANDLE(p->z) TSRMLS_CC);
812 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount--;
813 				}
814 			}
815 			count++;
816 			p = p->u.next;
817 		}
818 
819 		/* Destroy zvals */
820 		p = GC_G(free_list);
821 		while (p != FREE_LIST_END) {
822 			GC_G(next_to_free) = p->u.next;
823 			if (Z_TYPE(p->z) == IS_OBJECT) {
824 				if (EG(objects_store).object_buckets &&
825 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
826 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0) {
827 					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount = 1;
828 					Z_TYPE(p->z) = IS_NULL;
829 					zend_objects_store_del_ref_by_handle_ex(Z_OBJ_HANDLE(p->z), Z_OBJ_HT(p->z) TSRMLS_CC);
830 				}
831 			} else if (Z_TYPE(p->z) == IS_ARRAY) {
832 				Z_TYPE(p->z) = IS_NULL;
833 				zend_hash_destroy(Z_ARRVAL(p->z));
834 				FREE_HASHTABLE(Z_ARRVAL(p->z));
835 			} else {
836 				zval_dtor(&p->z);
837 				Z_TYPE(p->z) = IS_NULL;
838 			}
839 			p = GC_G(next_to_free);
840 		}
841 
842 		/* Free zvals */
843 		p = GC_G(free_list);
844 		while (p != FREE_LIST_END) {
845 			q = p->u.next;
846 			FREE_ZVAL_EX(&p->z);
847 			p = q;
848 		}
849 		GC_G(collected) += count;
850 		GC_G(free_list) = orig_free_list;
851 		GC_G(next_to_free) = orig_next_to_free;
852 	}
853 
854 	return count;
855 }
856 
857 /*
858  * Local variables:
859  * tab-width: 4
860  * c-basic-offset: 4
861  * indent-tabs-mode: t
862  * End:
863  */
864