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