Skip to content

Commit 8547fba

Browse files
committed
Root current opline result after GC
1 parent 4191843 commit 8547fba

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
@@ -2216,14 +2216,45 @@ static void zend_get_gc_buffer_release(void) {
22162216
* of the value, we would end up leaking it. To avoid this, root all live TMPVAR values here. */
22172217
static void zend_gc_check_root_tmpvars(void) {
22182218
zend_execute_data *ex = EG(current_execute_data);
2219+
2220+
if (!ex) {
2221+
return;
2222+
}
2223+
2224+
if (ZEND_USER_CODE(ex->func->type)) {
2225+
const zend_op *op = ex->opline;
2226+
if (op->result_type & (IS_VAR | IS_TMP_VAR)) {
2227+
switch (op->opcode) {
2228+
case ZEND_ADD_ARRAY_ELEMENT:
2229+
case ZEND_ADD_ARRAY_UNPACK:
2230+
case ZEND_ROPE_INIT:
2231+
case ZEND_ROPE_ADD:
2232+
break; /* live range handling will check those */
2233+
2234+
case ZEND_FETCH_CLASS:
2235+
case ZEND_DECLARE_ANON_CLASS:
2236+
break; /* return value is zend_class_entry pointer */
2237+
2238+
default:
2239+
/* smart branch opcodes may not initialize result */
2240+
if (!zend_is_smart_branch(op)) {
2241+
zval *var = ZEND_CALL_VAR(ex, op->result.var);
2242+
if (Z_COLLECTABLE_P(var)) {
2243+
gc_check_possible_root(Z_COUNTED_P(var));
2244+
}
2245+
}
2246+
}
2247+
}
2248+
}
2249+
22192250
for (; ex; ex = ex->prev_execute_data) {
22202251
zend_function *func = ex->func;
22212252
if (!func || !ZEND_USER_CODE(func->type)) {
22222253
continue;
22232254
}
22242255

22252256
uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
2226-
for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
2257+
for (int i = 0; i < func->op_array.last_live_range; i++) {
22272258
const zend_live_range *range = &func->op_array.live_range[i];
22282259
if (range->start > op_num) {
22292260
break;
@@ -2246,14 +2277,45 @@ static void zend_gc_check_root_tmpvars(void) {
22462277

22472278
static void zend_gc_remove_root_tmpvars(void) {
22482279
zend_execute_data *ex = EG(current_execute_data);
2280+
2281+
if (!ex) {
2282+
return;
2283+
}
2284+
2285+
if (ZEND_USER_CODE(ex->func->type)) {
2286+
const zend_op *op = ex->opline;
2287+
if (op->result_type & (IS_VAR | IS_TMP_VAR)) {
2288+
switch (op->opcode) {
2289+
case ZEND_ADD_ARRAY_ELEMENT:
2290+
case ZEND_ADD_ARRAY_UNPACK:
2291+
case ZEND_ROPE_INIT:
2292+
case ZEND_ROPE_ADD:
2293+
break; /* live range handling will remove those */
2294+
2295+
case ZEND_FETCH_CLASS:
2296+
case ZEND_DECLARE_ANON_CLASS:
2297+
break; /* return value is zend_class_entry pointer */
2298+
2299+
default:
2300+
/* smart branch opcodes may not initialize result */
2301+
if (!zend_is_smart_branch(op)) {
2302+
zval *var = ZEND_CALL_VAR(ex, op->result.var);
2303+
if (Z_COLLECTABLE_P(var)) {
2304+
GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var));
2305+
}
2306+
}
2307+
}
2308+
}
2309+
}
2310+
22492311
for (; ex; ex = ex->prev_execute_data) {
22502312
zend_function *func = ex->func;
22512313
if (!func || !ZEND_USER_CODE(func->type)) {
22522314
continue;
22532315
}
22542316

22552317
uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
2256-
for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
2318+
for (int i = 0; i < func->op_array.last_live_range; i++) {
22572319
const zend_live_range *range = &func->op_array.live_range[i];
22582320
if (range->start > op_num) {
22592321
break;

0 commit comments

Comments
 (0)