Skip to content

Commit d451c52

Browse files
committed
Root current opline result after GC
1 parent c9cc68b commit d451c52

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed

Zend/tests/gc/gh13687-001.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
GH-13687 001: Result operand may leak if GC is triggered before consumption
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public $cycle;
8+
public function __construct() { $this->cycle = $this; }
9+
}
10+
class B {
11+
public function get() {
12+
return new A();
13+
}
14+
}
15+
16+
$c = new B();
17+
$objs = [];
18+
19+
while (gc_status()['roots']+2 < gc_status()['threshold']) {
20+
$obj = new stdClass;
21+
$objs[] = $obj;
22+
}
23+
24+
var_dump($c->get());
25+
26+
?>
27+
--EXPECTF--
28+
object(A)#%d (1) {
29+
["cycle"]=>
30+
*RECURSION*
31+
}

Zend/tests/gc/gh13687-002.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
GH-13687 002: Result operand may leak if GC is triggered before consumption
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public $cycle;
8+
public function __construct() { $this->cycle = $this; }
9+
public function __toString() { return __CLASS__; }
10+
}
11+
class B {
12+
public function get() {
13+
return new A();
14+
}
15+
}
16+
17+
$root = new stdClass;
18+
gc_collect_cycles();
19+
20+
$objs = [];
21+
while (gc_status()['roots']+2 < gc_status()['threshold']) {
22+
$obj = new stdClass;
23+
$objs[] = $obj;
24+
}
25+
26+
$a = [new A, $root][0]::class;
27+
28+
?>
29+
==DONE==
30+
--EXPECT--
31+
==DONE==

Zend/zend_gc.c

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,14 +2045,45 @@ static void zend_get_gc_buffer_release(void) {
20452045
* of the value, we would end up leaking it. To avoid this, root all live TMPVAR values here. */
20462046
static void zend_gc_check_root_tmpvars(void) {
20472047
zend_execute_data *ex = EG(current_execute_data);
2048+
2049+
if (!ex) {
2050+
return;
2051+
}
2052+
2053+
if (ZEND_USER_CODE(ex->func->type)) {
2054+
const zend_op *op = ex->opline;
2055+
if (op->result_type & (IS_VAR | IS_TMP_VAR)) {
2056+
switch (op->opcode) {
2057+
case ZEND_ADD_ARRAY_ELEMENT:
2058+
case ZEND_ADD_ARRAY_UNPACK:
2059+
case ZEND_ROPE_INIT:
2060+
case ZEND_ROPE_ADD:
2061+
break; /* live range handling will check those */
2062+
2063+
case ZEND_FETCH_CLASS:
2064+
case ZEND_DECLARE_ANON_CLASS:
2065+
break; /* return value is zend_class_entry pointer */
2066+
2067+
default:
2068+
/* smart branch opcodes may not initialize result */
2069+
if (!zend_is_smart_branch(op)) {
2070+
zval *var = ZEND_CALL_VAR(ex, op->result.var);
2071+
if (Z_COLLECTABLE_P(var)) {
2072+
gc_check_possible_root(Z_COUNTED_P(var));
2073+
}
2074+
}
2075+
}
2076+
}
2077+
}
2078+
20482079
for (; ex; ex = ex->prev_execute_data) {
20492080
zend_function *func = ex->func;
20502081
if (!func || !ZEND_USER_CODE(func->type)) {
20512082
continue;
20522083
}
20532084

20542085
uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
2055-
for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
2086+
for (int i = 0; i < func->op_array.last_live_range; i++) {
20562087
const zend_live_range *range = &func->op_array.live_range[i];
20572088
if (range->start > op_num) {
20582089
break;
@@ -2075,14 +2106,45 @@ static void zend_gc_check_root_tmpvars(void) {
20752106

20762107
static void zend_gc_remove_root_tmpvars(void) {
20772108
zend_execute_data *ex = EG(current_execute_data);
2109+
2110+
if (!ex) {
2111+
return;
2112+
}
2113+
2114+
if (ZEND_USER_CODE(ex->func->type)) {
2115+
const zend_op *op = ex->opline;
2116+
if (op->result_type & (IS_VAR | IS_TMP_VAR)) {
2117+
switch (op->opcode) {
2118+
case ZEND_ADD_ARRAY_ELEMENT:
2119+
case ZEND_ADD_ARRAY_UNPACK:
2120+
case ZEND_ROPE_INIT:
2121+
case ZEND_ROPE_ADD:
2122+
break; /* live range handling will remove those */
2123+
2124+
case ZEND_FETCH_CLASS:
2125+
case ZEND_DECLARE_ANON_CLASS:
2126+
break; /* return value is zend_class_entry pointer */
2127+
2128+
default:
2129+
/* smart branch opcodes may not initialize result */
2130+
if (!zend_is_smart_branch(op)) {
2131+
zval *var = ZEND_CALL_VAR(ex, op->result.var);
2132+
if (Z_COLLECTABLE_P(var)) {
2133+
GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var));
2134+
}
2135+
}
2136+
}
2137+
}
2138+
}
2139+
20782140
for (; ex; ex = ex->prev_execute_data) {
20792141
zend_function *func = ex->func;
20802142
if (!func || !ZEND_USER_CODE(func->type)) {
20812143
continue;
20822144
}
20832145

20842146
uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
2085-
for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
2147+
for (int i = 0; i < func->op_array.last_live_range; i++) {
20862148
const zend_live_range *range = &func->op_array.live_range[i];
20872149
if (range->start > op_num) {
20882150
break;

0 commit comments

Comments
 (0)