Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d451c52

Browse files
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)

AltStyle によって変換されたページ (->オリジナル) /