Skip to content

Commit c43f5a6

Browse files
committed
Merge branch 'PHP-8.6'
* PHP-8.6: Update NEWS ext/standard/tests: work around Windows quoting with tempfiles. .github/scripts/windows: pass an extension_dir to the test runner. {ext/standard,sapi/cli}/tests: pass TEST_PHP_EXTRA_ARGS to php ext/standard/tests: pass TEST_PHP_EXTRA_ARGS to some proc_open() tests sapi/cli/tests/023.phpt: support TEST_PHP_EXTRA_ARGS std: Fix memory leak of the last response headers after a nested HTTP request (#24201) Fiber: hold the object a callable resolved to in the creating frame (#24134)
2 parents 23bb1c9 + 3e458e5 commit c43f5a6

17 files changed

Lines changed: 223 additions & 52 deletions

‎.github/scripts/windows/test_task.bat‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ set OPENSSL_CONF=
9090
rem set SSLEAY_CONF=
9191

9292
rem prepare for OPcache
93-
if "%OPCACHE%" equ "1" set OPCACHE_OPTS=-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.protect_memory=1 -d opcache.jit_buffer_size=64M -d opcache.jit=tracing
93+
set PHP_BUILD_DIR=%PHP_BUILD_OBJ_DIR%\Release
94+
if "%THREAD_SAFE%" equ "1" set PHP_BUILD_DIR=%PHP_BUILD_DIR%_TS
95+
96+
if "%OPCACHE%" equ "1" set OPCACHE_OPTS=-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.protect_memory=1 -d opcache.jit_buffer_size=64M -d opcache.jit=tracing -d extension_dir=%PHP_BUILD_DIR%
9497

9598
rem prepare for enchant
9699
mkdir %~d0\usr\local\lib\enchant-2
@@ -111,9 +114,6 @@ set MIBDIRS=%DEPS_DIR%\share\mibs
111114
sed -i "s/exec HexTest .*/exec HexTest cscript\.exe \/nologo %GITHUB_WORKSPACE:\=\/%\/ext\/snmp\/tests\/bigtest\.js/g" %GITHUB_WORKSPACE%\ext\snmp\tests\snmpd.conf
112115
start %DEPS_DIR%\bin\snmpd.exe -C -c %GITHUB_WORKSPACE%\ext\snmp\tests\snmpd.conf -Ln
113116

114-
set PHP_BUILD_DIR=%PHP_BUILD_OBJ_DIR%\Release
115-
if "%THREAD_SAFE%" equ "1" set PHP_BUILD_DIR=%PHP_BUILD_DIR%_TS
116-
117117
rem prepare for mail
118118
curl -sLo hMailServer.zip https://downloads.php.net/~windows/php-sdk/deps/vs18/x64/hmailserver-5.7.0-vs18-x64.zip
119119
unzip -q hMailServer.zip -d hMailServer
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
--TEST--
2+
Fiber holds the object a callable resolved to in the creating method ($this for [A::class, 'm'] or "A::m")
3+
--FILE--
4+
<?php
5+
class A {
6+
public ?Fiber $fiber = null;
7+
8+
public function __construct(public string $name) {}
9+
10+
public function m() {
11+
echo "m on {$this->name}\n";
12+
}
13+
14+
public function waits() {
15+
Fiber::suspend();
16+
echo "resumed on {$this->name}\n";
17+
}
18+
19+
public function make(string $method) {
20+
return new Fiber([A::class, $method]);
21+
}
22+
23+
public function makeFromString() {
24+
return new Fiber("A::m");
25+
}
26+
27+
public function keep() {
28+
$this->fiber = new Fiber([A::class, 'm']);
29+
}
30+
31+
public function __destruct() {
32+
echo "released {$this->name}\n";
33+
}
34+
}
35+
36+
echo "-- started after its creator's last reference went\n";
37+
$fiber = (new A("a1"))->make('m');
38+
$fiber->start();
39+
echo "after start\n";
40+
41+
echo "-- a string callable\n";
42+
$fiber = (new A("a2"))->makeFromString();
43+
$fiber->start();
44+
echo "after start\n";
45+
46+
echo "-- never started, released with the fiber\n";
47+
$fiber = (new A("a3"))->make('m');
48+
unset($fiber);
49+
echo "after unset\n";
50+
51+
echo "-- suspended while its object has no other reference\n";
52+
$a = new A("a4");
53+
$fiber = $a->make('waits');
54+
$fiber->start();
55+
unset($a);
56+
$fiber->resume();
57+
echo "after resume\n";
58+
59+
echo "-- a cycle through the fiber is collected\n";
60+
$a = new A("a5");
61+
$a->keep();
62+
unset($a);
63+
var_dump(gc_collect_cycles());
64+
echo "end\n";
65+
?>
66+
--EXPECT--
67+
-- started after its creator's last reference went
68+
m on a1
69+
released a1
70+
after start
71+
-- a string callable
72+
m on a2
73+
released a2
74+
after start
75+
-- never started, released with the fiber
76+
released a3
77+
after unset
78+
-- suspended while its object has no other reference
79+
resumed on a4
80+
released a4
81+
after resume
82+
-- a cycle through the fiber is collected
83+
released a5
84+
int(2)
85+
end

‎Zend/zend_fibers.c‎

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,39 @@ static void zend_fiber_cleanup(zend_fiber_context *context)
563563
fiber->caller = NULL;
564564
}
565565

566+
static void zend_fiber_addref_callable(zend_fiber *fiber)
567+
{
568+
// Keep a reference to closures or callable objects while the fiber is running.
569+
Z_TRY_ADDREF(fiber->fci.function_name);
570+
571+
/* For [A::class, 'm'] or "A::m" inside a method of A, fci.object is the caller's $this. */
572+
if (fiber->fci.object != NULL) {
573+
GC_ADDREF(fiber->fci.object);
574+
}
575+
}
576+
577+
static void zend_fiber_get_gc_callable(zend_fiber *fiber, zend_get_gc_buffer *buf)
578+
{
579+
zend_get_gc_buffer_add_zval(buf, &fiber->fci.function_name);
580+
581+
if (fiber->fci.object != NULL) {
582+
zend_get_gc_buffer_add_obj(buf, fiber->fci.object);
583+
}
584+
}
585+
586+
static void zend_fiber_release_callable(zend_fiber *fiber)
587+
{
588+
zval_ptr_dtor(&fiber->fci.function_name);
589+
ZVAL_UNDEF(&fiber->fci.function_name);
590+
fiber->fci_cache = empty_fcall_info_cache;
591+
592+
if (fiber->fci.object != NULL) {
593+
zend_object *object = fiber->fci.object;
594+
fiber->fci.object = NULL;
595+
OBJ_RELEASE(object);
596+
}
597+
}
598+
566599
static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
567600
{
568601
ZEND_ASSERT(Z_TYPE(transfer->value) == IS_NULL && "Initial transfer value to fiber context must be NULL");
@@ -605,8 +638,7 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
605638
zend_call_function(&fiber->fci, &fiber->fci_cache);
606639

607640
/* Cleanup callback and unset field to prevent GC / duplicate dtor issues. */
608-
zval_ptr_dtor(&fiber->fci.function_name);
609-
ZVAL_UNDEF(&fiber->fci.function_name);
641+
zend_fiber_release_callable(fiber);
610642

611643
if (EG(exception)) {
612644
if (!(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)
@@ -808,7 +840,7 @@ static void zend_fiber_object_free(zend_object *object)
808840
{
809841
zend_fiber *fiber = (zend_fiber *) object;
810842

811-
zval_ptr_dtor(&fiber->fci.function_name);
843+
zend_fiber_release_callable(fiber);
812844
zval_ptr_dtor(&fiber->result);
813845

814846
zend_object_std_dtor(&fiber->std);
@@ -819,7 +851,7 @@ static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *n
819851
zend_fiber *fiber = (zend_fiber *) object;
820852
zend_get_gc_buffer *buf = zend_get_gc_buffer_create();
821853

822-
zend_get_gc_buffer_add_zval(buf, &fiber->fci.function_name);
854+
zend_fiber_get_gc_callable(fiber, buf);
823855
zend_get_gc_buffer_add_zval(buf, &fiber->result);
824856

825857
if (fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL) {
@@ -887,8 +919,7 @@ ZEND_METHOD(Fiber, __construct)
887919
fiber->fci = fci;
888920
fiber->fci_cache = fcc;
889921

890-
// Keep a reference to closures or callable objects while the fiber is running.
891-
Z_TRY_ADDREF(fiber->fci.function_name);
922+
zend_fiber_addref_callable(fiber);
892923
}
893924

894925
ZEND_METHOD(Fiber, start)

‎ext/standard/http_fopen_wrapper.c‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,16 +1277,15 @@ php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, const char *pa
12771277

12781278
ZVAL_UNDEF(&headers);
12791279

1280-
zval_ptr_dtor(&BG(last_http_headers));
1281-
ZVAL_UNDEF(&BG(last_http_headers));
1282-
12831280
stream = php_stream_url_wrap_http_ex(
12841281
wrapper, path, mode, options, opened_path, context,
12851282
PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT, &headers STREAMS_CC);
12861283

1287-
if (!Z_ISUNDEF(headers)) {
1288-
ZVAL_COPY(&BG(last_http_headers), &headers);
1284+
/* After the request: one made from its notification callback may have stored its own headers here. */
1285+
zval_ptr_dtor(&BG(last_http_headers));
1286+
ZVAL_COPY(&BG(last_http_headers), &headers);
12891287

1288+
if (!Z_ISUNDEF(headers)) {
12901289
if (FAILURE == zend_set_local_var_str(
12911290
"http_response_header", sizeof("http_response_header")-1, &headers, 0)) {
12921291
zval_ptr_dtor(&headers);

‎ext/standard/tests/file/bug60120.phpt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ if (PHP_OS_FAMILY === 'Windows') die('skip not for Windows');
1414
error_reporting(E_ALL);
1515

1616
$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
17-
$cmd = $php . ' -r "\$in = file_get_contents(\'php://stdin\'); fwrite(STDOUT, \$in); fwrite(STDERR, \$in);"';
17+
$args = getenv('TEST_PHP_EXTRA_ARGS');
18+
$cmd = ' -r "\$in = file_get_contents(\'php://stdin\'); fwrite(STDOUT, \$in); fwrite(STDERR, \$in);"';
19+
$cmd = join(' ', [$php, $args, $cmd]);
1820
$descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'));
1921
$stdin = str_repeat('*', 2049 );
2022

‎ext/standard/tests/general_functions/proc_open_null.phpt‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ Null pipes in proc_open()
44
<?php
55

66
$php = getenv('TEST_PHP_EXECUTABLE');
7-
$cmd = [$php, '-r', 'echo "Test"; fprintf(STDERR, "Error");'];
7+
$args = getenv('TEST_PHP_EXTRA_ARGS');
8+
$fn = tempnam(sys_get_temp_dir(), "PROC_OPEN_TEST");
9+
$cmd = "$php $args " . escapeshellarg($fn);
10+
file_put_contents($fn, '<?php echo "Test"; fprintf(STDERR, "Error");');
811

912
$proc = proc_open($cmd, [1 => ['null'], 2 => ['pipe', 'w']], $pipes);
1013
var_dump($pipes);
@@ -15,16 +18,17 @@ $proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['null']], $pipes);
1518
var_dump($pipes);
1619
var_dump(stream_get_contents($pipes[1]));
1720
proc_close($proc);
21+
unlink($fn);
1822

1923
?>
2024
--EXPECT--
2125
array(1) {
2226
[2]=>
23-
resource(4) of type (stream)
27+
resource(6) of type (stream)
2428
}
2529
string(5) "Error"
2630
array(1) {
2731
[1]=>
28-
resource(6) of type (stream)
32+
resource(8) of type (stream)
2933
}
3034
string(4) "Test"

‎ext/standard/tests/general_functions/proc_open_redirect.phpt‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,31 @@ Redirection support in proc_open
44
<?php
55

66
$php = getenv('TEST_PHP_EXECUTABLE');
7+
$args = getenv('TEST_PHP_EXTRA_ARGS');
8+
$cmd = "$php $args";
9+
710
try {
8-
proc_open([$php], [['redirect']], $pipes);
11+
proc_open($cmd, [['redirect']], $pipes);
912
} catch (Throwable $exception) {
1013
echo $exception::class, ': ', $exception->getMessage(), "\n";
1114
}
1215

1316
try {
14-
proc_open([$php], [['redirect', 'foo']], $pipes);
17+
proc_open($cmd, [['redirect', 'foo']], $pipes);
1518
} catch (Throwable $exception) {
1619
echo $exception::class, ': ', $exception->getMessage(), "\n";
1720
}
1821

1922
try {
20-
proc_open([$php], [['redirect', 42]], $pipes);
23+
proc_open($cmd, [['redirect', 42]], $pipes);
2124
} catch (Throwable $exception) {
2225
echo $exception::class, ': ', $exception->getMessage(), "\n";
2326
}
2427

2528
echo "\nWith pipe:\n";
26-
$cmd = [$php, '-r', 'echo "Test\n"; fprintf(STDERR, "Error");'];
29+
$fn = tempnam(sys_get_temp_dir(), "PROC_OPEN_TEST");
30+
file_put_contents($fn, '<?php echo "Test\n"; fprintf(STDERR, "Error");');
31+
$cmd = "$php $args " . escapeshellarg($fn);
2732
$proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes);
2833
var_dump($pipes);
2934
var_dump(stream_get_contents($pipes[1]));
@@ -49,6 +54,7 @@ unlink($fileName);
4954
echo "\nWith inherited stdout:\n";
5055
$proc = proc_open($cmd, [2 => ['redirect', 1]], $pipes);
5156
proc_close($proc);
57+
unlink($fn);
5258

5359
?>
5460
--EXPECTF--
@@ -60,7 +66,7 @@ Warning: proc_open(): Redirection target 42 not found in %s
6066
With pipe:
6167
array(1) {
6268
[1]=>
63-
resource(4) of type (stream)
69+
resource(6) of type (stream)
6470
}
6571
string(10) "Test
6672
Error"

‎ext/standard/tests/general_functions/proc_open_sockets1.phpt‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
77
--FILE--
88
<?php
99

10-
$cmd = [
11-
getenv("TEST_PHP_EXECUTABLE"),
12-
__DIR__ . '/proc_open_sockets1.inc'
13-
];
10+
$php = getenv("TEST_PHP_EXECUTABLE");
11+
$args = getenv("TEST_PHP_EXTRA_ARGS");
12+
$cmd = "$php $args " . __DIR__ . '/proc_open_sockets1.inc';
1413

1514
$spec = [
1615
['null'],

‎ext/standard/tests/general_functions/proc_open_sockets2.phpt‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ function write_pipe($pipe, $data)
3434
}
3535
}
3636

37-
$cmd = [
38-
getenv("TEST_PHP_EXECUTABLE"),
39-
__DIR__ . '/proc_open_sockets2.inc'
40-
];
37+
$php = getenv("TEST_PHP_EXECUTABLE");
38+
$args = getenv("TEST_PHP_EXTRA_ARGS");
39+
$cmd = "$php $args " . __DIR__ . '/proc_open_sockets2.inc';
4140

4241
$spec = [
4342
['socket'],

‎ext/standard/tests/general_functions/proc_open_sockets3.phpt‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ function read_pipe($pipe): string
2525
return $chunk;
2626
}
2727

28-
$cmd = [
29-
getenv("TEST_PHP_EXECUTABLE"),
30-
__DIR__ . '/proc_open_sockets2.inc'
31-
];
28+
$php = getenv("TEST_PHP_EXECUTABLE");
29+
$args = getenv("TEST_PHP_EXTRA_ARGS");
30+
$cmd = "$php $args " . __DIR__ . '/proc_open_sockets2.inc';
3231

3332
$spec = [
3433
['pipe', 'r'],

0 commit comments

Comments
 (0)