Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Zend/tests/gc/bug70805.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ $i = 0;
$c = new A;
$array = array($c); //This is used to leave a room for $GLOBALS["a"]
unset($c);
flush(); // handle interrupts

while ($i++ < 9998) {
$t = [];
Expand All @@ -37,11 +38,13 @@ while ($i++ < 9998) {
$t = [new C];
$t[] = &$t;
unset($t); // This is used to trigger C::__destruct while doing gc_collect_roots
flush(); // handle interrupts

$e = $a;
unset($a); // This one cannot be put into roots buf because it's full, thus gc_collect_roots will be called,
// but C::__destructor which is called in gc_collect_roots will put $a into buf
// which will make $a be put into gc roots buf twice
flush(); // handle interrupts
var_dump(gc_collect_cycles());
?>
--EXPECT--
Expand Down
5 changes: 4 additions & 1 deletion Zend/tests/gc/bug70805_1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ $i = 0;
$c = new A;
$array = array($c);
unset($c);
flush(); // handle interrupts

while ($i++ < 9998) {
$t = [];
Expand All @@ -39,9 +40,11 @@ while ($i++ < 9998) {
$t = [new C];
$t[] = &$t;
unset($t);
flush(); // handle interrupts
unset($a);
flush(); // handle interrupts

var_dump(gc_collect_cycles());
?>
--EXPECT--
int(2)
int(0)
5 changes: 4 additions & 1 deletion Zend/tests/gc/bug70805_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ while ($i++ < 9999) {
$t[] = &$t;
unset($t);
}
flush(); // handle interrupts
$t = [new C];
$t[] = &$t;
unset($t);
flush(); // handle interrupts

unset($a);
flush(); // handle interrupts
var_dump(gc_collect_cycles());
?>
--EXPECT--
int(2)
int(0)
7 changes: 5 additions & 2 deletions Zend/tests/gc/gc_023.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ for ($i=0; $i < 9999; $i++) {
}
var_dump(gc_collect_cycles());
unset($a);
flush(); // handle interrupts
var_dump(gc_collect_cycles());
$a=array();
for ($i=0; $i < 10001; $i++) {
$a[$i] = array(array());
$a[$i][0] = & $a[$i];
}
flush(); // handle interrupts
var_dump(gc_collect_cycles());
unset($a); // 10000 zvals collected automatic
unset($a);
flush(); // handle interrupts. 10000 zvals collected automatic
var_dump(gc_collect_cycles());
echo "ok\n";
?>
--EXPECT--
int(0)
int(9999)
int(0)
int(1)
int(0)
ok
6 changes: 3 additions & 3 deletions Zend/tests/gc/gc_045.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ array(12) {
["runs"]=>
int(10)
["collected"]=>
int(25000)
int(25005)
["threshold"]=>
int(10001)
["buffer_size"]=>
int(16384)
int(%d)
["roots"]=>
int(10000)
int(9990)
["application_time"]=>
float(%f)
["collector_time"]=>
Expand Down
31 changes: 31 additions & 0 deletions Zend/tests/gc/gh13687-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
GH-13687 001: Result operand may leak if GC is triggered before consumption
--FILE--
<?php

class A {
public $cycle;
public function __construct() { $this->cycle = $this; }
}
class B {
public function get() {
return new A();
}
}

$c = new B();
$objs = [];

while (gc_status()['roots']+2 < gc_status()['threshold']) {
$obj = new stdClass;
$objs[] = $obj;
}

var_dump($c->get());

?>
--EXPECTF--
object(A)#%d (1) {
["cycle"]=>
*RECURSION*
}
31 changes: 31 additions & 0 deletions Zend/tests/gc/gh13687-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
GH-13687 002: Result operand may leak if GC is triggered before consumption
--FILE--
<?php

class A {
public $cycle;
public function __construct() { $this->cycle = $this; }
public function __toString() { return __CLASS__; }
}
class B {
public function get() {
return new A();
}
}

$root = new stdClass;
gc_collect_cycles();

$objs = [];
while (gc_status()['roots']+2 < gc_status()['threshold']) {
$obj = new stdClass;
$objs[] = $obj;
}

$a = [new A, $root][0]::class;

?>
==DONE==
--EXPECT--
==DONE==
34 changes: 34 additions & 0 deletions Zend/tests/gc/gh13687-003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
GH-13687 003: Result operand may leak if GC is triggered before consumption
--FILE--
<?php

class A {
public $cycle;
public function __construct() { $this->cycle = $this; }
}
class B {
public function get() {
return new A();
}
}
class Debug {
public function __debugInfo() {
gc_collect_cycles();
return [];
}
}

$c = new B();
var_dump($c->get(), new Debug);

?>
==DONE==
--EXPECTF--
object(A)#%d (1) {
["cycle"]=>
*RECURSION*
}
object(Debug)#%d (0) {
}
==DONE==
30 changes: 30 additions & 0 deletions Zend/tests/gc/gh13687-004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
GH-13687 004: Result operand may leak if GC is triggered before consumption
--ENV--
func=call_user_func
--FILE--
<?php

class A {
public $cycle;
public function __construct() { $this->cycle = $this; }
}
class B {
public function get() {
return new A();
}
}
class Debug {
public function __debugInfo() {
gc_collect_cycles();
return [];
}
}

$c = new B();
getenv('func')(fn (...$args) => gc_collect_cycles(), a: $c->get());

?>
==DONE==
--EXPECT--
==DONE==
37 changes: 37 additions & 0 deletions Zend/tests/gc/gh13687-005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
GH-13687 005: Result operand may leak if GC is triggered before consumption
--FILE--
<?php

class A {
public $cycle;
public function __construct() {
$this->cycle = $this;
$a = $this;
unset($a);
}
}
class B {
public function get() {
return new A();
}
}

$c = new B();
$objs = [];

$rc = new ReflectionClass(A::class);

while (gc_status()['roots']+1 < gc_status()['threshold']) {
$obj = new stdClass;
$objs[] = $obj;
}

var_dump($rc->newInstance());

?>
--EXPECTF--
object(A)#%d (1) {
["cycle"]=>
*RECURSION*
}
2 changes: 1 addition & 1 deletion Zend/zend_builtin_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ ZEND_FUNCTION(gc_collect_cycles)
{
ZEND_PARSE_PARAMETERS_NONE();

RETURN_LONG(gc_collect_cycles());
RETURN_LONG(gc_collect_cycles(0));
}
/* }}} */

Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -4269,6 +4269,8 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *ca
zend_atomic_bool_store_ex(&EG(vm_interrupt), false);
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
zend_timeout();
} else if (zend_atomic_bool_load_ex(&EG(gc_requested))) {
gc_run_from_fcall_interrupt();
} else if (zend_interrupt_function) {
zend_interrupt_function(call);
}
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown)

#if ZEND_DEBUG
if (!CG(unclean_shutdown)) {
gc_collect_cycles();
gc_collect_cycles(0);
}
#endif
} else {
Expand Down
Loading
Loading