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 8547fba

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

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