xref: /PHP-7.0/Zend/zend_gc.c (revision 549a30d2)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine                                                          |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1998-2017 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 /* one (0) is reserved */
26 #define GC_ROOT_BUFFER_MAX_ENTRIES 10001
27 
28 #define GC_HAS_DESTRUCTORS  (1<<0)
29 
30 #ifndef ZEND_GC_DEBUG
31 # define ZEND_GC_DEBUG 0
32 #endif
33 
34 #ifdef ZTS
35 ZEND_API int gc_globals_id;
36 #else
37 ZEND_API zend_gc_globals gc_globals;
38 #endif
39 
40 ZEND_API int (*gc_collect_cycles)(void);
41 
42 #if ZEND_GC_DEBUG > 1
43 # define GC_TRACE(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__);
44 # define GC_TRACE_REF(ref, format, ...) \
45 	do { \
46 		gc_trace_ref((zend_refcounted *) ref); \
47 		fprintf(stderr, format "\n", ##__VA_ARGS__); \
48 	} while (0)
49 # define GC_TRACE_SET_COLOR(ref, color) \
50 	GC_TRACE_REF(ref, "->%s", gc_color_name(color))
51 #else
52 # define GC_TRACE_REF(ref, format, ...)
53 # define GC_TRACE_SET_COLOR(ref, new_color)
54 # define GC_TRACE(str)
55 #endif
56 
57 #define GC_REF_SET_ADDRESS(ref, a) \
58 	GC_INFO_SET_ADDRESS(GC_INFO(ref), a)
59 #define GC_REF_GET_COLOR(ref) \
60 	GC_INFO_GET_COLOR(GC_INFO(ref))
61 #define GC_REF_SET_COLOR(ref, c) \
62 	do { GC_TRACE_SET_COLOR(ref, c); GC_INFO_SET_COLOR(GC_INFO(ref), c); } while (0)
63 #define GC_REF_SET_BLACK(ref) \
64 	do { GC_TRACE_SET_COLOR(ref, GC_BLACK); GC_INFO_SET_BLACK(GC_INFO(ref)); } while (0)
65 #define GC_REF_SET_PURPLE(ref) \
66 	do { GC_TRACE_SET_COLOR(ref, GC_PURPLE); GC_INFO_SET_PURPLE(GC_INFO(ref)); } while (0)
67 
68 #if ZEND_GC_DEBUG > 1
gc_color_name(uint32_t color)69 static const char *gc_color_name(uint32_t color) {
70 	switch (color) {
71 		case GC_BLACK: return "black";
72 		case GC_WHITE: return "white";
73 		case GC_GREY: return "grey";
74 		case GC_PURPLE: return "purple";
75 		default: return "unknown";
76 	}
77 }
gc_trace_ref(zend_refcounted * ref)78 static void gc_trace_ref(zend_refcounted *ref) {
79 	if (GC_TYPE(ref) == IS_OBJECT) {
80 		zend_object *obj = (zend_object *) ref;
81 		fprintf(stderr, "[%p] rc=%d addr=%d %s object(%s)#%d ",
82 			ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
83 			gc_color_name(GC_REF_GET_COLOR(ref)),
84 			obj->ce->name->val, obj->handle);
85 	} else if (GC_TYPE(ref) == IS_ARRAY) {
86 		zend_array *arr = (zend_array *) ref;
87 		fprintf(stderr, "[%p] rc=%d addr=%d %s array(%d) ",
88 			ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
89 			gc_color_name(GC_REF_GET_COLOR(ref)),
90 			zend_hash_num_elements(arr));
91 	} else {
92 		fprintf(stderr, "[%p] rc=%d addr=%d %s %s ",
93 			ref, GC_REFCOUNT(ref), GC_ADDRESS(GC_INFO(ref)),
94 			gc_color_name(GC_REF_GET_COLOR(ref)),
95 			zend_get_type_by_const(GC_TYPE(ref)));
96 	}
97 }
98 #endif
99 
gc_remove_from_roots(gc_root_buffer * root)100 static zend_always_inline void gc_remove_from_roots(gc_root_buffer *root)
101 {
102 	root->next->prev = root->prev;
103 	root->prev->next = root->next;
104 	root->prev = GC_G(unused);
105 	GC_G(unused) = root;
106 	GC_BENCH_DEC(root_buf_length);
107 }
108 
gc_remove_from_additional_roots(gc_root_buffer * root)109 static zend_always_inline void gc_remove_from_additional_roots(gc_root_buffer *root)
110 {
111 	root->next->prev = root->prev;
112 	root->prev->next = root->next;
113 }
114 
root_buffer_dtor(zend_gc_globals * gc_globals)115 static void root_buffer_dtor(zend_gc_globals *gc_globals)
116 {
117 	if (gc_globals->buf) {
118 		free(gc_globals->buf);
119 		gc_globals->buf = NULL;
120 	}
121 }
122 
gc_globals_ctor_ex(zend_gc_globals * gc_globals)123 static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
124 {
125 	gc_globals->gc_enabled = 0;
126 	gc_globals->gc_active = 0;
127 
128 	gc_globals->buf = NULL;
129 
130 	gc_globals->roots.next = &gc_globals->roots;
131 	gc_globals->roots.prev = &gc_globals->roots;
132 	gc_globals->unused = NULL;
133 	gc_globals->next_to_free = NULL;
134 
135 	gc_globals->to_free.next = &gc_globals->to_free;
136 	gc_globals->to_free.prev = &gc_globals->to_free;
137 
138 	gc_globals->gc_runs = 0;
139 	gc_globals->collected = 0;
140 
141 	gc_globals->additional_buffer = NULL;
142 
143 #if GC_BENCH
144 	gc_globals->root_buf_length = 0;
145 	gc_globals->root_buf_peak = 0;
146 	gc_globals->zval_possible_root = 0;
147 	gc_globals->zval_buffered = 0;
148 	gc_globals->zval_remove_from_buffer = 0;
149 	gc_globals->zval_marked_grey = 0;
150 #endif
151 }
152 
gc_globals_ctor(void)153 ZEND_API void gc_globals_ctor(void)
154 {
155 #ifdef ZTS
156 	ts_allocate_id(&gc_globals_id, sizeof(zend_gc_globals), (ts_allocate_ctor) gc_globals_ctor_ex, (ts_allocate_dtor) root_buffer_dtor);
157 #else
158 	gc_globals_ctor_ex(&gc_globals);
159 #endif
160 }
161 
gc_globals_dtor(void)162 ZEND_API void gc_globals_dtor(void)
163 {
164 #ifndef ZTS
165 	root_buffer_dtor(&gc_globals);
166 #endif
167 }
168 
gc_reset(void)169 ZEND_API void gc_reset(void)
170 {
171 	GC_G(gc_runs) = 0;
172 	GC_G(collected) = 0;
173 	GC_G(gc_full) = 0;
174 
175 #if GC_BENCH
176 	GC_G(root_buf_length) = 0;
177 	GC_G(root_buf_peak) = 0;
178 	GC_G(zval_possible_root) = 0;
179 	GC_G(zval_buffered) = 0;
180 	GC_G(zval_remove_from_buffer) = 0;
181 	GC_G(zval_marked_grey) = 0;
182 #endif
183 
184 	GC_G(roots).next = &GC_G(roots);
185 	GC_G(roots).prev = &GC_G(roots);
186 
187 	GC_G(to_free).next = &GC_G(to_free);
188 	GC_G(to_free).prev = &GC_G(to_free);
189 
190 	if (GC_G(buf)) {
191 		GC_G(unused) = NULL;
192 		GC_G(first_unused) = GC_G(buf) + 1;
193 	} else {
194 		GC_G(unused) = NULL;
195 		GC_G(first_unused) = NULL;
196 		GC_G(last_unused) = NULL;
197 	}
198 
199 	GC_G(additional_buffer) = NULL;
200 }
201 
gc_init(void)202 ZEND_API void gc_init(void)
203 {
204 	if (GC_G(buf) == NULL && GC_G(gc_enabled)) {
205 		GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);
206 		GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
207 		gc_reset();
208 	}
209 }
210 
gc_possible_root(zend_refcounted * ref)211 ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
212 {
213 	gc_root_buffer *newRoot;
214 
215 	if (UNEXPECTED(CG(unclean_shutdown)) || UNEXPECTED(GC_G(gc_active))) {
216 		return;
217 	}
218 
219 	ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
220 	ZEND_ASSERT(EXPECTED(GC_REF_GET_COLOR(ref) == GC_BLACK));
221 	ZEND_ASSERT(!GC_ADDRESS(GC_INFO(ref)));
222 
223 	GC_BENCH_INC(zval_possible_root);
224 
225 	newRoot = GC_G(unused);
226 	if (newRoot) {
227 		GC_G(unused) = newRoot->prev;
228 	} else if (GC_G(first_unused) != GC_G(last_unused)) {
229 		newRoot = GC_G(first_unused);
230 		GC_G(first_unused)++;
231 	} else {
232 		if (!GC_G(gc_enabled)) {
233 			return;
234 		}
235 		GC_REFCOUNT(ref)++;
236 		gc_collect_cycles();
237 		GC_REFCOUNT(ref)--;
238 		if (UNEXPECTED(GC_REFCOUNT(ref)) == 0) {
239 			zval_dtor_func_for_ptr(ref);
240 			return;
241 		}
242 		if (UNEXPECTED(GC_INFO(ref))) {
243 			return;
244 		}
245 		newRoot = GC_G(unused);
246 		if (!newRoot) {
247 #if ZEND_GC_DEBUG
248 			if (!GC_G(gc_full)) {
249 				fprintf(stderr, "GC: no space to record new root candidate\n");
250 				GC_G(gc_full) = 1;
251 			}
252 #endif
253 			return;
254 		}
255 		GC_G(unused) = newRoot->prev;
256 	}
257 
258 	GC_TRACE_SET_COLOR(ref, GC_PURPLE);
259 	GC_INFO(ref) = (newRoot - GC_G(buf)) | GC_PURPLE;
260 	newRoot->ref = ref;
261 
262 	newRoot->next = GC_G(roots).next;
263 	newRoot->prev = &GC_G(roots);
264 	GC_G(roots).next->prev = newRoot;
265 	GC_G(roots).next = newRoot;
266 
267 	GC_BENCH_INC(zval_buffered);
268 	GC_BENCH_INC(root_buf_length);
269 	GC_BENCH_PEAK(root_buf_peak, root_buf_length);
270 }
271 
gc_find_additional_buffer(zend_refcounted * ref)272 static zend_always_inline gc_root_buffer* gc_find_additional_buffer(zend_refcounted *ref)
273 {
274 	gc_additional_buffer *additional_buffer = GC_G(additional_buffer);
275 
276 	/* We have to check each additional_buffer to find which one holds the ref */
277 	while (additional_buffer) {
278 		uint32_t idx = GC_ADDRESS(GC_INFO(ref)) - GC_ROOT_BUFFER_MAX_ENTRIES;
279 		if (idx < additional_buffer->used) {
280 			gc_root_buffer *root = additional_buffer->buf + idx;
281 			if (root->ref == ref) {
282 				return root;
283 			}
284 		}
285 		additional_buffer = additional_buffer->next;
286 	}
287 
288 	ZEND_ASSERT(0);
289 	return NULL;
290 }
291 
gc_remove_from_buffer(zend_refcounted * ref)292 ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
293 {
294 	gc_root_buffer *root;
295 
296 	ZEND_ASSERT(GC_ADDRESS(GC_INFO(ref)));
297 
298 	GC_BENCH_INC(zval_remove_from_buffer);
299 
300 	if (EXPECTED(GC_ADDRESS(GC_INFO(ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
301 		root = GC_G(buf) + GC_ADDRESS(GC_INFO(ref));
302 		gc_remove_from_roots(root);
303 	} else {
304 		root = gc_find_additional_buffer(ref);
305 		gc_remove_from_additional_roots(root);
306 	}
307 	if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
308 		GC_TRACE_SET_COLOR(ref, GC_PURPLE);
309 	}
310 	GC_INFO(ref) = 0;
311 
312 	/* updete next root that is going to be freed */
313 	if (GC_G(next_to_free) == root) {
314 		GC_G(next_to_free) = root->next;
315 	}
316 }
317 
gc_scan_black(zend_refcounted * ref)318 static void gc_scan_black(zend_refcounted *ref)
319 {
320 	HashTable *ht;
321 	Bucket *p, *end;
322 	zval *zv;
323 
324 tail_call:
325 	ht = NULL;
326 	GC_REF_SET_BLACK(ref);
327 
328 	if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
329 		zend_object_get_gc_t get_gc;
330 		zend_object *obj = (zend_object*)ref;
331 
332 		if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
333 		             (get_gc = obj->handlers->get_gc) != NULL)) {
334 			int n;
335 			zval *zv, *end;
336 			zval tmp;
337 
338 			ZVAL_OBJ(&tmp, obj);
339 			ht = get_gc(&tmp, &zv, &n);
340 			end = zv + n;
341 			if (EXPECTED(!ht)) {
342 				if (!n) return;
343 				while (!Z_REFCOUNTED_P(--end)) {
344 					if (zv == end) return;
345 				}
346 			}
347 			while (zv != end) {
348 				if (Z_REFCOUNTED_P(zv)) {
349 					ref = Z_COUNTED_P(zv);
350 					GC_REFCOUNT(ref)++;
351 					if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
352 						gc_scan_black(ref);
353 					}
354 				}
355 				zv++;
356 			}
357 			if (EXPECTED(!ht)) {
358 				ref = Z_COUNTED_P(zv);
359 				GC_REFCOUNT(ref)++;
360 				if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
361 					goto tail_call;
362 				}
363 				return;
364 			}
365 		} else {
366 			return;
367 		}
368 	} else if (GC_TYPE(ref) == IS_ARRAY) {
369 		if ((zend_array*)ref != &EG(symbol_table)) {
370 			ht = (zend_array*)ref;
371 		} else {
372 			return;
373 		}
374 	} else if (GC_TYPE(ref) == IS_REFERENCE) {
375 		if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
376 			ref = Z_COUNTED(((zend_reference*)ref)->val);
377 			GC_REFCOUNT(ref)++;
378 			if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
379 				goto tail_call;
380 			}
381 		}
382 		return;
383 	} else {
384 		return;
385 	}
386 
387 	if (!ht->nNumUsed) return;
388 	p = ht->arData;
389 	end = p + ht->nNumUsed;
390 	while (1) {
391 		end--;
392 		zv = &end->val;
393 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
394 			zv = Z_INDIRECT_P(zv);
395 		}
396 		if (Z_REFCOUNTED_P(zv)) {
397 			break;
398 		}
399 		if (p == end) return;
400 	}
401 	while (p != end) {
402 		zv = &p->val;
403 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
404 			zv = Z_INDIRECT_P(zv);
405 		}
406 		if (Z_REFCOUNTED_P(zv)) {
407 			ref = Z_COUNTED_P(zv);
408 			GC_REFCOUNT(ref)++;
409 			if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
410 				gc_scan_black(ref);
411 			}
412 		}
413 		p++;
414 	}
415 	zv = &p->val;
416 	if (Z_TYPE_P(zv) == IS_INDIRECT) {
417 		zv = Z_INDIRECT_P(zv);
418 	}
419 	ref = Z_COUNTED_P(zv);
420 	GC_REFCOUNT(ref)++;
421 	if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
422 		goto tail_call;
423 	}
424 }
425 
gc_mark_grey(zend_refcounted * ref)426 static void gc_mark_grey(zend_refcounted *ref)
427 {
428     HashTable *ht;
429 	Bucket *p, *end;
430 	zval *zv;
431 
432 tail_call:
433 	if (GC_REF_GET_COLOR(ref) != GC_GREY) {
434 		ht = NULL;
435 		GC_BENCH_INC(zval_marked_grey);
436 		GC_REF_SET_COLOR(ref, GC_GREY);
437 
438 		if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
439 			zend_object_get_gc_t get_gc;
440 			zend_object *obj = (zend_object*)ref;
441 
442 			if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
443 		             (get_gc = obj->handlers->get_gc) != NULL)) {
444 				int n;
445 				zval *zv, *end;
446 				zval tmp;
447 
448 				ZVAL_OBJ(&tmp, obj);
449 				ht = get_gc(&tmp, &zv, &n);
450 				end = zv + n;
451 				if (EXPECTED(!ht)) {
452 					if (!n) return;
453 					while (!Z_REFCOUNTED_P(--end)) {
454 						if (zv == end) return;
455 					}
456 				}
457 				while (zv != end) {
458 					if (Z_REFCOUNTED_P(zv)) {
459 						ref = Z_COUNTED_P(zv);
460 						GC_REFCOUNT(ref)--;
461 						gc_mark_grey(ref);
462 					}
463 					zv++;
464 				}
465 				if (EXPECTED(!ht)) {
466 					ref = Z_COUNTED_P(zv);
467 					GC_REFCOUNT(ref)--;
468 					goto tail_call;
469 				}
470 			} else {
471 				return;
472 			}
473 		} else if (GC_TYPE(ref) == IS_ARRAY) {
474 			if (((zend_array*)ref) == &EG(symbol_table)) {
475 				GC_REF_SET_BLACK(ref);
476 				return;
477 			} else {
478 				ht = (zend_array*)ref;
479 			}
480 		} else if (GC_TYPE(ref) == IS_REFERENCE) {
481 			if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
482 				if (UNEXPECTED(!EG(objects_store).object_buckets) &&
483 					Z_TYPE(((zend_reference*)ref)->val) == IS_OBJECT) {
484 					Z_TYPE_INFO(((zend_reference*)ref)->val) = IS_NULL;
485 					return;
486 				}
487 				ref = Z_COUNTED(((zend_reference*)ref)->val);
488 				GC_REFCOUNT(ref)--;
489 				goto tail_call;
490 			}
491 			return;
492 		} else {
493 			return;
494 		}
495 
496 		if (!ht->nNumUsed) return;
497 		p = ht->arData;
498 		end = p + ht->nNumUsed;
499 		while (1) {
500 			end--;
501 			zv = &end->val;
502 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
503 				zv = Z_INDIRECT_P(zv);
504 			}
505 			if (Z_REFCOUNTED_P(zv)) {
506 				break;
507 			}
508 			if (p == end) return;
509 		}
510 		while (p != end) {
511 			zv = &p->val;
512 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
513 				zv = Z_INDIRECT_P(zv);
514 			}
515 			if (Z_REFCOUNTED_P(zv)) {
516 				if (Z_TYPE_P(zv) == IS_OBJECT &&
517 				    UNEXPECTED(!EG(objects_store).object_buckets)) {
518 					Z_TYPE_INFO_P(zv) = IS_NULL;
519 				} else {
520 					ref = Z_COUNTED_P(zv);
521 					GC_REFCOUNT(ref)--;
522 					gc_mark_grey(ref);
523 				}
524 			}
525 			p++;
526 		}
527 		zv = &p->val;
528 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
529 			zv = Z_INDIRECT_P(zv);
530 		}
531 		if (Z_TYPE_P(zv) == IS_OBJECT &&
532 		    UNEXPECTED(!EG(objects_store).object_buckets)) {
533 			Z_TYPE_INFO_P(zv) = IS_NULL;
534 		} else {
535 			ref = Z_COUNTED_P(zv);
536 			GC_REFCOUNT(ref)--;
537 			goto tail_call;
538 		}
539 	}
540 }
541 
gc_mark_roots(void)542 static void gc_mark_roots(void)
543 {
544 	gc_root_buffer *current = GC_G(roots).next;
545 
546 	while (current != &GC_G(roots)) {
547 		if (GC_REF_GET_COLOR(current->ref) == GC_PURPLE) {
548 			gc_mark_grey(current->ref);
549 		}
550 		current = current->next;
551 	}
552 }
553 
gc_scan(zend_refcounted * ref)554 static void gc_scan(zend_refcounted *ref)
555 {
556     HashTable *ht;
557 	Bucket *p, *end;
558 	zval *zv;
559 
560 tail_call:
561 	if (GC_REF_GET_COLOR(ref) == GC_GREY) {
562 		if (GC_REFCOUNT(ref) > 0) {
563 			gc_scan_black(ref);
564 		} else {
565 			GC_REF_SET_COLOR(ref, GC_WHITE);
566 			if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
567 				zend_object_get_gc_t get_gc;
568 				zend_object *obj = (zend_object*)ref;
569 
570 				if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
571 				             (get_gc = obj->handlers->get_gc) != NULL)) {
572 					int n;
573 					zval *zv, *end;
574 					zval tmp;
575 
576 					ZVAL_OBJ(&tmp, obj);
577 					ht = get_gc(&tmp, &zv, &n);
578 					end = zv + n;
579 					if (EXPECTED(!ht)) {
580 						if (!n) return;
581 						while (!Z_REFCOUNTED_P(--end)) {
582 							if (zv == end) return;
583 						}
584 					}
585 					while (zv != end) {
586 						if (Z_REFCOUNTED_P(zv)) {
587 							ref = Z_COUNTED_P(zv);
588 							gc_scan(ref);
589 						}
590 						zv++;
591 					}
592 					if (EXPECTED(!ht)) {
593 						ref = Z_COUNTED_P(zv);
594 						goto tail_call;
595 					}
596 				} else {
597 					return;
598 				}
599 			} else if (GC_TYPE(ref) == IS_ARRAY) {
600 				if ((zend_array*)ref == &EG(symbol_table)) {
601 					GC_REF_SET_BLACK(ref);
602 					return;
603 				} else {
604 					ht = (zend_array*)ref;
605 				}
606 			} else if (GC_TYPE(ref) == IS_REFERENCE) {
607 				if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
608 					ref = Z_COUNTED(((zend_reference*)ref)->val);
609 					goto tail_call;
610 				}
611 				return;
612 			} else {
613 				return;
614 			}
615 
616 			if (!ht->nNumUsed) return;
617 			p = ht->arData;
618 			end = p + ht->nNumUsed;
619 			while (1) {
620 				end--;
621 				zv = &end->val;
622 				if (Z_TYPE_P(zv) == IS_INDIRECT) {
623 					zv = Z_INDIRECT_P(zv);
624 				}
625 				if (Z_REFCOUNTED_P(zv)) {
626 					break;
627 				}
628 				if (p == end) return;
629 			}
630 			while (p != end) {
631 				zv = &p->val;
632 				if (Z_TYPE_P(zv) == IS_INDIRECT) {
633 					zv = Z_INDIRECT_P(zv);
634 				}
635 				if (Z_REFCOUNTED_P(zv)) {
636 					ref = Z_COUNTED_P(zv);
637 					gc_scan(ref);
638 				}
639 				p++;
640 			}
641 			zv = &p->val;
642 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
643 				zv = Z_INDIRECT_P(zv);
644 			}
645 			ref = Z_COUNTED_P(zv);
646 			goto tail_call;
647 		}
648 	}
649 }
650 
gc_scan_roots(void)651 static void gc_scan_roots(void)
652 {
653 	gc_root_buffer *current = GC_G(roots).next;
654 
655 	while (current != &GC_G(roots)) {
656 		gc_scan(current->ref);
657 		current = current->next;
658 	}
659 }
660 
gc_add_garbage(zend_refcounted * ref)661 static void gc_add_garbage(zend_refcounted *ref)
662 {
663 	gc_root_buffer *buf = GC_G(unused);
664 
665 	if (buf) {
666 		GC_G(unused) = buf->prev;
667 #if 1
668 		/* optimization: color is already GC_BLACK (0) */
669 		GC_INFO(ref) = buf - GC_G(buf);
670 #else
671 		GC_REF_SET_ADDRESS(ref, buf - GC_G(buf));
672 #endif
673 	} else if (GC_G(first_unused) != GC_G(last_unused)) {
674 		buf = GC_G(first_unused);
675 		GC_G(first_unused)++;
676 #if 1
677 		/* optimization: color is already GC_BLACK (0) */
678 		GC_INFO(ref) = buf - GC_G(buf);
679 #else
680 		GC_REF_SET_ADDRESS(ref, buf - GC_G(buf));
681 #endif
682 	} else {
683 		/* If we don't have free slots in the buffer, allocate a new one and
684 		 * set it's address above GC_ROOT_BUFFER_MAX_ENTRIES that have special
685 		 * meaning.
686 		 */
687 		if (!GC_G(additional_buffer) || GC_G(additional_buffer)->used == GC_NUM_ADDITIONAL_ENTRIES) {
688 			gc_additional_buffer *new_buffer = emalloc(sizeof(gc_additional_buffer));
689 			new_buffer->used = 0;
690 			new_buffer->next = GC_G(additional_buffer);
691 			GC_G(additional_buffer) = new_buffer;
692 		}
693 		buf = GC_G(additional_buffer)->buf + GC_G(additional_buffer)->used;
694 #if 1
695 		/* optimization: color is already GC_BLACK (0) */
696 		GC_INFO(ref) = GC_ROOT_BUFFER_MAX_ENTRIES + GC_G(additional_buffer)->used;
697 #else
698 		GC_REF_SET_ADDRESS(ref, GC_ROOT_BUFFER_MAX_ENTRIES) + GC_G(additional_buffer)->used;
699 #endif
700 		GC_G(additional_buffer)->used++;
701 	}
702 	if (buf) {
703 		GC_REFCOUNT(ref)++;
704 		buf->ref = ref;
705 		buf->next = GC_G(roots).next;
706 		buf->prev = &GC_G(roots);
707 		GC_G(roots).next->prev = buf;
708 		GC_G(roots).next = buf;
709 	}
710 }
711 
gc_collect_white(zend_refcounted * ref,uint32_t * flags)712 static int gc_collect_white(zend_refcounted *ref, uint32_t *flags)
713 {
714 	int count = 0;
715 	HashTable *ht;
716 	Bucket *p, *end;
717 	zval *zv;
718 
719 tail_call:
720 	if (GC_REF_GET_COLOR(ref) == GC_WHITE) {
721 		ht = NULL;
722 		GC_REF_SET_BLACK(ref);
723 
724 		/* don't count references for compatibility ??? */
725 		if (GC_TYPE(ref) != IS_REFERENCE) {
726 			count++;
727 		}
728 
729 		if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
730 			zend_object_get_gc_t get_gc;
731 			zend_object *obj = (zend_object*)ref;
732 
733 			if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
734 			             (get_gc = obj->handlers->get_gc) != NULL)) {
735 				int n;
736 				zval *zv, *end;
737 				zval tmp;
738 
739 #if 1
740 				/* optimization: color is GC_BLACK (0) */
741 				if (!GC_INFO(ref)) {
742 #else
743 				if (!GC_ADDRESS(GC_INFO(ref))) {
744 #endif
745 					gc_add_garbage(ref);
746 				}
747 				if (obj->handlers->dtor_obj &&
748 				    ((obj->handlers->dtor_obj != zend_objects_destroy_object) ||
749 				     (obj->ce->destructor != NULL))) {
750 					*flags |= GC_HAS_DESTRUCTORS;
751 				}
752 				ZVAL_OBJ(&tmp, obj);
753 				ht = get_gc(&tmp, &zv, &n);
754 				end = zv + n;
755 				if (EXPECTED(!ht)) {
756 					if (!n) return count;
757 					while (!Z_REFCOUNTED_P(--end)) {
758 						/* count non-refcounted for compatibility ??? */
759 						if (Z_TYPE_P(zv) != IS_UNDEF) {
760 							count++;
761 						}
762 						if (zv == end) return count;
763 					}
764 				}
765 				while (zv != end) {
766 					if (Z_REFCOUNTED_P(zv)) {
767 						ref = Z_COUNTED_P(zv);
768 						GC_REFCOUNT(ref)++;
769 						count += gc_collect_white(ref, flags);
770 					/* count non-refcounted for compatibility ??? */
771 					} else if (Z_TYPE_P(zv) != IS_UNDEF) {
772 						count++;
773 					}
774 					zv++;
775 				}
776 				if (EXPECTED(!ht)) {
777 					ref = Z_COUNTED_P(zv);
778 					GC_REFCOUNT(ref)++;
779 					goto tail_call;
780 				}
781 			} else {
782 				return count;
783 			}
784 		} else if (GC_TYPE(ref) == IS_ARRAY) {
785 #if 1
786 				/* optimization: color is GC_BLACK (0) */
787 				if (!GC_INFO(ref)) {
788 #else
789 				if (!GC_ADDRESS(GC_INFO(ref))) {
790 #endif
791 				gc_add_garbage(ref);
792 			}
793 			ht = (zend_array*)ref;
794 		} else if (GC_TYPE(ref) == IS_REFERENCE) {
795 			if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
796 				ref = Z_COUNTED(((zend_reference*)ref)->val);
797 				GC_REFCOUNT(ref)++;
798 				goto tail_call;
799 			}
800 			return count;
801 		} else {
802 			return count;
803 		}
804 
805 		if (!ht->nNumUsed) return count;
806 		p = ht->arData;
807 		end = p + ht->nNumUsed;
808 		while (1) {
809 			end--;
810 			zv = &end->val;
811 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
812 				zv = Z_INDIRECT_P(zv);
813 			}
814 			if (Z_REFCOUNTED_P(zv)) {
815 				break;
816 			}
817 			/* count non-refcounted for compatibility ??? */
818 			if (Z_TYPE_P(zv) != IS_UNDEF) {
819 				count++;
820 			}
821 			if (p == end) return count;
822 		}
823 		while (p != end) {
824 			zv = &p->val;
825 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
826 				zv = Z_INDIRECT_P(zv);
827 			}
828 			if (Z_REFCOUNTED_P(zv)) {
829 				ref = Z_COUNTED_P(zv);
830 				GC_REFCOUNT(ref)++;
831 				count += gc_collect_white(ref, flags);
832 				/* count non-refcounted for compatibility ??? */
833 			} else if (Z_TYPE_P(zv) != IS_UNDEF) {
834 				count++;
835 			}
836 			p++;
837 		}
838 		zv = &p->val;
839 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
840 			zv = Z_INDIRECT_P(zv);
841 		}
842 		ref = Z_COUNTED_P(zv);
843 		GC_REFCOUNT(ref)++;
844 		goto tail_call;
845 	}
846 	return count;
847 }
848 
849 static int gc_collect_roots(uint32_t *flags)
850 {
851 	int count = 0;
852 	gc_root_buffer *current = GC_G(roots).next;
853 
854 	/* remove non-garbage from the list */
855 	while (current != &GC_G(roots)) {
856 		gc_root_buffer *next = current->next;
857 		if (GC_REF_GET_COLOR(current->ref) == GC_BLACK) {
858 			if (EXPECTED(GC_ADDRESS(GC_INFO(current->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
859 				gc_remove_from_roots(current);
860 			} else {
861 				gc_remove_from_additional_roots(current);
862 			}
863 			GC_INFO(current->ref) = 0; /* reset GC_ADDRESS() and keep GC_BLACK */
864 		}
865 		current = next;
866 	}
867 
868 	current = GC_G(roots).next;
869 	while (current != &GC_G(roots)) {
870 		GC_REFCOUNT(current->ref)++;
871 		if (GC_REF_GET_COLOR(current->ref) == GC_WHITE) {
872 			count += gc_collect_white(current->ref, flags);
873 		}
874 		current = current->next;
875 	}
876 
877 	/* relink remaining roots into list to free */
878 	if (GC_G(roots).next != &GC_G(roots)) {
879 		if (GC_G(to_free).next == &GC_G(to_free)) {
880 			/* move roots into list to free */
881 			GC_G(to_free).next = GC_G(roots).next;
882 			GC_G(to_free).prev = GC_G(roots).prev;
883 			GC_G(to_free).next->prev = &GC_G(to_free);
884 			GC_G(to_free).prev->next = &GC_G(to_free);
885 		} else {
886 			/* add roots into list to free */
887 			GC_G(to_free).prev->next = GC_G(roots).next;
888 			GC_G(roots).next->prev = GC_G(to_free).prev;
889 			GC_G(roots).prev->next = &GC_G(to_free);
890 			GC_G(to_free).prev = GC_G(roots).prev;
891 		}
892 
893 		GC_G(roots).next = &GC_G(roots);
894 		GC_G(roots).prev = &GC_G(roots);
895 	}
896 	return count;
897 }
898 
899 static void gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root)
900 {
901 	HashTable *ht = NULL;
902 	Bucket *p, *end;
903 	zval *zv;
904 
905 tail_call:
906 	if (root ||
907 	    (GC_ADDRESS(GC_INFO(ref)) != 0 &&
908 	     GC_REF_GET_COLOR(ref) == GC_BLACK)) {
909 		GC_TRACE_REF(ref, "removing from buffer");
910 		GC_REFCOUNT(ref)--;
911 		if (root) {
912 			if (EXPECTED(GC_ADDRESS(GC_INFO(root->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
913 				gc_remove_from_roots(root);
914 			} else {
915 				gc_remove_from_additional_roots(root);
916 			}
917 			GC_INFO(ref) = 0;
918 			root = NULL;
919 		} else {
920 			GC_REMOVE_FROM_BUFFER(ref);
921 		}
922 
923 		if (GC_TYPE(ref) == IS_OBJECT && !(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED)) {
924 			zend_object_get_gc_t get_gc;
925 			zend_object *obj = (zend_object*)ref;
926 
927 			if (EXPECTED(IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
928 		             (get_gc = obj->handlers->get_gc) != NULL)) {
929 				int n;
930 				zval *zv, *end;
931 				zval tmp;
932 
933 				ZVAL_OBJ(&tmp, obj);
934 				ht = get_gc(&tmp, &zv, &n);
935 				end = zv + n;
936 				if (EXPECTED(!ht)) {
937 					if (!n) return;
938 					while (!Z_REFCOUNTED_P(--end)) {
939 						if (zv == end) return;
940 					}
941 				}
942 				while (zv != end) {
943 					if (Z_REFCOUNTED_P(zv)) {
944 						ref = Z_COUNTED_P(zv);
945 						gc_remove_nested_data_from_buffer(ref, NULL);
946 					}
947 					zv++;
948 				}
949 				if (EXPECTED(!ht)) {
950 					ref = Z_COUNTED_P(zv);
951 					goto tail_call;
952 				}
953 			} else {
954 				return;
955 			}
956 		} else if (GC_TYPE(ref) == IS_ARRAY) {
957 			ht = (zend_array*)ref;
958 		} else if (GC_TYPE(ref) == IS_REFERENCE) {
959 			if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
960 				ref = Z_COUNTED(((zend_reference*)ref)->val);
961 				goto tail_call;
962 			}
963 			return;
964 		} else {
965 			return;
966 		}
967 
968 		if (!ht->nNumUsed) return;
969 		p = ht->arData;
970 		end = p + ht->nNumUsed;
971 		while (1) {
972 			end--;
973 			zv = &end->val;
974 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
975 				zv = Z_INDIRECT_P(zv);
976 			}
977 			if (Z_REFCOUNTED_P(zv)) {
978 				break;
979 			}
980 			if (p == end) return;
981 		}
982 		while (p != end) {
983 			zv = &p->val;
984 			if (Z_TYPE_P(zv) == IS_INDIRECT) {
985 				zv = Z_INDIRECT_P(zv);
986 			}
987 			if (Z_REFCOUNTED_P(zv)) {
988 				ref = Z_COUNTED_P(zv);
989 				gc_remove_nested_data_from_buffer(ref, NULL);
990 			}
991 			p++;
992 		}
993 		zv = &p->val;
994 		if (Z_TYPE_P(zv) == IS_INDIRECT) {
995 			zv = Z_INDIRECT_P(zv);
996 		}
997 		ref = Z_COUNTED_P(zv);
998 		goto tail_call;
999 	}
1000 }
1001 
1002 ZEND_API int zend_gc_collect_cycles(void)
1003 {
1004 	int count = 0;
1005 
1006 	if (GC_G(roots).next != &GC_G(roots)) {
1007 		gc_root_buffer *current, *next, *orig_next_to_free;
1008 		zend_refcounted *p;
1009 		gc_root_buffer to_free;
1010 		uint32_t gc_flags = 0;
1011 		gc_additional_buffer *additional_buffer_snapshot;
1012 #if ZEND_GC_DEBUG
1013 		zend_bool orig_gc_full;
1014 #endif
1015 
1016 		if (GC_G(gc_active)) {
1017 			return 0;
1018 		}
1019 
1020 		GC_TRACE("Collecting cycles");
1021 		GC_G(gc_runs)++;
1022 		GC_G(gc_active) = 1;
1023 
1024 		GC_TRACE("Marking roots");
1025 		gc_mark_roots();
1026 		GC_TRACE("Scanning roots");
1027 		gc_scan_roots();
1028 
1029 #if ZEND_GC_DEBUG
1030 		orig_gc_full = GC_G(gc_full);
1031 		GC_G(gc_full) = 0;
1032 #endif
1033 
1034 		GC_TRACE("Collecting roots");
1035 		additional_buffer_snapshot = GC_G(additional_buffer);
1036 		count = gc_collect_roots(&gc_flags);
1037 #if ZEND_GC_DEBUG
1038 		GC_G(gc_full) = orig_gc_full;
1039 #endif
1040 		GC_G(gc_active) = 0;
1041 
1042 		if (GC_G(to_free).next == &GC_G(to_free)) {
1043 			/* nothing to free */
1044 			GC_TRACE("Nothing to free");
1045 			return 0;
1046 		}
1047 
1048 		/* Copy global to_free list into local list */
1049 		to_free.next = GC_G(to_free).next;
1050 		to_free.prev = GC_G(to_free).prev;
1051 		to_free.next->prev = &to_free;
1052 		to_free.prev->next = &to_free;
1053 
1054 		/* Free global list */
1055 		GC_G(to_free).next = &GC_G(to_free);
1056 		GC_G(to_free).prev = &GC_G(to_free);
1057 
1058 		orig_next_to_free = GC_G(next_to_free);
1059 
1060 #if ZEND_GC_DEBUG
1061 		orig_gc_full = GC_G(gc_full);
1062 		GC_G(gc_full) = 0;
1063 #endif
1064 
1065 		if (gc_flags & GC_HAS_DESTRUCTORS) {
1066 			GC_TRACE("Calling destructors");
1067 			if (EG(objects_store).object_buckets) {
1068 				/* Remember reference counters before calling destructors */
1069 				current = to_free.next;
1070 				while (current != &to_free) {
1071 					current->refcount = GC_REFCOUNT(current->ref);
1072 					current = current->next;
1073 				}
1074 
1075 				/* Call destructors */
1076 				current = to_free.next;
1077 				while (current != &to_free) {
1078 					p = current->ref;
1079 					GC_G(next_to_free) = current->next;
1080 					if (GC_TYPE(p) == IS_OBJECT) {
1081 						zend_object *obj = (zend_object*)p;
1082 
1083 						if (IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
1084 							!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) {
1085 							GC_TRACE_REF(obj, "calling destructor");
1086 							GC_FLAGS(obj) |= IS_OBJ_DESTRUCTOR_CALLED;
1087 							if (obj->handlers->dtor_obj) {
1088 								GC_REFCOUNT(obj)++;
1089 								obj->handlers->dtor_obj(obj);
1090 								GC_REFCOUNT(obj)--;
1091 							}
1092 						}
1093 					}
1094 					current = GC_G(next_to_free);
1095 				}
1096 
1097 				/* Remove values captured in destructors */
1098 				current = to_free.next;
1099 				while (current != &to_free) {
1100 					GC_G(next_to_free) = current->next;
1101 					if (GC_REFCOUNT(current->ref) > current->refcount) {
1102 						gc_remove_nested_data_from_buffer(current->ref, current);
1103 					}
1104 					current = GC_G(next_to_free);
1105 				}
1106 			}
1107 		}
1108 
1109 		/* Destroy zvals */
1110 		GC_TRACE("Destroying zvals");
1111 		GC_G(gc_active) = 1;
1112 		current = to_free.next;
1113 		while (current != &to_free) {
1114 			p = current->ref;
1115 			GC_G(next_to_free) = current->next;
1116 			GC_TRACE_REF(p, "destroying");
1117 			if (GC_TYPE(p) == IS_OBJECT) {
1118 				zend_object *obj = (zend_object*)p;
1119 
1120 				if (EG(objects_store).object_buckets &&
1121 				    IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle])) {
1122 					EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj);
1123 					GC_TYPE(obj) = IS_NULL;
1124 					if (!(GC_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
1125 						GC_FLAGS(obj) |= IS_OBJ_FREE_CALLED;
1126 						if (obj->handlers->free_obj) {
1127 							GC_REFCOUNT(obj)++;
1128 							obj->handlers->free_obj(obj);
1129 							GC_REFCOUNT(obj)--;
1130 						}
1131 					}
1132 					SET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[obj->handle], EG(objects_store).free_list_head);
1133 					EG(objects_store).free_list_head = obj->handle;
1134 					p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset);
1135 				}
1136 			} else if (GC_TYPE(p) == IS_ARRAY) {
1137 				zend_array *arr = (zend_array*)p;
1138 
1139 				GC_TYPE(arr) = IS_NULL;
1140 				zend_hash_destroy(arr);
1141 			}
1142 			current = GC_G(next_to_free);
1143 		}
1144 
1145 		/* Free objects */
1146 		current = to_free.next;
1147 		while (current != &to_free) {
1148 			next = current->next;
1149 			p = current->ref;
1150 			if (EXPECTED(current >= GC_G(buf) && current < GC_G(buf) + GC_ROOT_BUFFER_MAX_ENTRIES)) {
1151 				current->prev = GC_G(unused);
1152 				GC_G(unused) = current;
1153 			}
1154 			efree(p);
1155 			current = next;
1156 		}
1157 
1158 		while (GC_G(additional_buffer) != additional_buffer_snapshot) {
1159 			gc_additional_buffer *next = GC_G(additional_buffer)->next;
1160 			efree(GC_G(additional_buffer));
1161 			GC_G(additional_buffer) = next;
1162 		}
1163 
1164 		GC_TRACE("Collection finished");
1165 		GC_G(collected) += count;
1166 		GC_G(next_to_free) = orig_next_to_free;
1167 #if ZEND_GC_DEBUG
1168 		GC_G(gc_full) = orig_gc_full;
1169 #endif
1170 		GC_G(gc_active) = 0;
1171 	}
1172 
1173 	return count;
1174 }
1175 
1176 /*
1177  * Local variables:
1178  * tab-width: 4
1179  * c-basic-offset: 4
1180  * indent-tabs-mode: t
1181  * End:
1182  */
1183