From b255ef686aff12a05262f963b7dc9c02c3f98a2a Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:03:16 +0700 Subject: [PATCH 01/17] fix: isolate rmdir test fixtures (#23225) --- .../tests/file/rmdir_variation3-win32.phpt | 33 ++++++++++--------- ext/standard/tests/file/rmdir_variation3.phpt | 7 ++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ext/standard/tests/file/rmdir_variation3-win32.phpt b/ext/standard/tests/file/rmdir_variation3-win32.phpt index 4f39ae5caf4d..154daf8c6810 100644 --- a/ext/standard/tests/file/rmdir_variation3-win32.phpt +++ b/ext/standard/tests/file/rmdir_variation3-win32.phpt @@ -11,7 +11,7 @@ if(substr(PHP_OS, 0, 3) != "WIN") +--CLEAN-- + --EXPECTF-- *** Testing rmdir() : variation *** --- removing rmdirVar3.tmp\aSubDir -- +-- removing rmdirVar3Win32.tmp\aSubDir -- Directory removed --- removing .\rmdirVar3.tmp\aSubDir -- +-- removing .\rmdirVar3Win32.tmp\aSubDir -- Directory removed --- removing rmdirVar3.tmp\..\rmdirVar3.tmp\aSubDir -- +-- removing rmdirVar3Win32.tmp\..\rmdirVar3Win32.tmp\aSubDir -- Directory removed --- removing rmdirVar3.tmp\..\BADDIR\aSubDir -- +-- removing rmdirVar3Win32.tmp\..\BADDIR\aSubDir -- Warning: rmdir(): No such file or directory in %s on line %d -- removing BADDIR\aSubDir -- Warning: rmdir(): No such file or directory in %s on line %d --- removing %s\rmdirVar3.tmp\aSubDir -- +-- removing %s\rmdirVar3Win32.tmp\aSubDir -- Directory removed --- removing %s\.\rmdirVar3.tmp\aSubDir -- +-- removing %s\.\rmdirVar3Win32.tmp\aSubDir -- Directory removed --- removing %s\rmdirVar3.tmp\..\rmdirVar3.tmp\aSubDir -- +-- removing %s\rmdirVar3Win32.tmp\..\rmdirVar3Win32.tmp\aSubDir -- Directory removed -- removing %s\BADDIR\aSubDir -- Warning: rmdir(): No such file or directory in %s on line %d --- removing rmdirVar3.tmp\aSubDir\ -- +-- removing rmdirVar3Win32.tmp\aSubDir\ -- Directory removed --- removing %s\rmdirVar3.tmp\aSubDir\ -- +-- removing %s\rmdirVar3Win32.tmp\aSubDir\ -- Directory removed --- removing rmdirVar3.tmp\\aSubDir -- +-- removing rmdirVar3Win32.tmp\\aSubDir -- Directory removed --- removing %s\\rmdirVar3.tmp\\aSubDir -- +-- removing %s\\rmdirVar3Win32.tmp\\aSubDir -- Directory removed --- removing /%s/rmdirVar3.tmp/aSubDir -- +-- removing /%s/rmdirVar3Win32.tmp/aSubDir -- Directory removed diff --git a/ext/standard/tests/file/rmdir_variation3.phpt b/ext/standard/tests/file/rmdir_variation3.phpt index 3e8cea123805..5f39f221ecf6 100644 --- a/ext/standard/tests/file/rmdir_variation3.phpt +++ b/ext/standard/tests/file/rmdir_variation3.phpt @@ -52,8 +52,11 @@ foreach($dirs as $dir) { } } -rmdir($workDir); - +?> +--CLEAN-- + --EXPECTF-- *** Testing rmdir() : variation *** From 304664212ea526c8a6e98c367cf3db297df75095 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 12 Aug 2026 18:13:36 +0800 Subject: [PATCH 02/17] ext/readline: Fixed the interactive shell not waiting for the pager process to exit (#23047) This backports 34a2949 to PHP 8.5 --- NEWS | 4 +++ ext/readline/readline_cli.c | 2 +- ext/readline/tests/readline_cli_pager.phpt | 29 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 ext/readline/tests/readline_cli_pager.phpt diff --git a/NEWS b/NEWS index c3454aba1a79..c18dfda62aa5 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.5.11 +- Readline: + . Fixed the interactive shell not waiting for the pager process to exit. + (Weilin Du) + 27 Aug 2026, PHP 8.5.10 diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c index ca4e8eb4fe9f..fb8f9a261637 100644 --- a/ext/readline/readline_cli.c +++ b/ext/readline/readline_cli.c @@ -712,7 +712,7 @@ static int readline_shell_run(void) /* {{{ */ } if (pager_pipe) { - fclose(pager_pipe); + pclose(pager_pipe); pager_pipe = NULL; } diff --git a/ext/readline/tests/readline_cli_pager.phpt b/ext/readline/tests/readline_cli_pager.phpt new file mode 100644 index 000000000000..2d5309472324 --- /dev/null +++ b/ext/readline/tests/readline_cli_pager.phpt @@ -0,0 +1,29 @@ +--TEST-- +Interactive shell: output through cli.pager +--EXTENSIONS-- +readline +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Interactive shell + +php > echo "pager output +php " "; +pager output +PAGER OUTPUT +php > quit From 257999bd33b89940cd8a6c2212a930429b74645d Mon Sep 17 00:00:00 2001 From: Shivam Mathur Date: Wed, 12 Aug 2026 15:51:26 +0530 Subject: [PATCH 03/17] Fix test file conflict on Windows (#23226) --- .../file_get_contents_variation7-win32.phpt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/standard/tests/file/file_get_contents_variation7-win32.phpt b/ext/standard/tests/file/file_get_contents_variation7-win32.phpt index a353630c9532..827c9e410b7f 100644 --- a/ext/standard/tests/file/file_get_contents_variation7-win32.phpt +++ b/ext/standard/tests/file/file_get_contents_variation7-win32.phpt @@ -10,8 +10,8 @@ if(substr(PHP_OS, 0, 3) != "WIN") --FILE-- --CLEAN-- Date: Wed, 8 Jul 2026 07:52:37 -0400 Subject: [PATCH 04/17] Fix GH-15375: nested "yield from" skips items after valid()/next() The DO_INIT guard in zend_generator_resume() that avoids re-advancing an already-primed delegate read the flag on orig_generator, but in a nested "yield from" chain the fresh delegation is established by a middle generator rather than the outermost consumer. Track the delegating generator through the resume loop and check its DO_INIT, so a primed inner generator reached through two "yield from" levels yields its current value once instead of skipping it. Fixes GH-15375 Closes GH-22640 --- NEWS | 3 + Zend/tests/generators/gh15375.phpt | 89 ++++++++++++++++++++++++++++++ Zend/zend_generators.c | 13 ++++- 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/generators/gh15375.phpt diff --git a/NEWS b/NEWS index 14149cee9c60..ffefe0d338ec 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.26 +- Core: + . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or + next() call on the inner generator). (iliaal) 27 Aug 2026, PHP 8.4.25 diff --git a/Zend/tests/generators/gh15375.phpt b/Zend/tests/generators/gh15375.phpt new file mode 100644 index 000000000000..48919083f227 --- /dev/null +++ b/Zend/tests/generators/gh15375.phpt @@ -0,0 +1,89 @@ +--TEST-- +GH-15375 (Nested "yield from" skips items after valid()/next() on the inner generator) +--FILE-- +valid()) { + yield from $iterator; + } + } +} + +function withNext() { + foreach (arrayProvider() as $array) { + $iterator = iterateValues($array); + $iterator->next(); + yield from $iterator; + } +} + +function outer(Generator $inner) { + yield from $inner; +} + +echo "valid():\n"; +foreach (outer(withValid()) as $s) { + echo $s, "\n"; +} + +echo "next():\n"; +foreach (outer(withNext()) as $s) { + echo $s, "\n"; +} + +// A shared, pre-primed generator consumed through two nested "yield from" +// levels must still present its current value once to each consumer (the fix +// must not over-clear the middle level's first-touch). +echo "shared primed:\n"; +function counter() { + yield 1; + yield 2; +} +$gen1 = counter(); +$gen1->valid(); +$gen2 = outer($gen1); +$gen3 = outer($gen2); +echo "gen3 current: ", $gen3->current(), "\n"; +$gen2->next(); +echo "gen2 current: ", $gen2->current(), "\n"; +$gen2->next(); +echo "gen2 current: ", $gen2->current(), "\n"; + +?> +--EXPECT-- +valid(): +one +two +three +four +five +six +seven +eight +nine +next(): +two +three +five +six +eight +nine +shared primed: +gen3 current: 1 +gen2 current: 1 +gen2 current: 2 diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index f7bcf01881dc..83815773b912 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -768,13 +768,15 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ return; } + zend_generator *delegator = orig_generator; + try_again: if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) { zend_throw_error(NULL, "Cannot resume an already running generator"); return; } - if (UNEXPECTED((orig_generator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) { + if (UNEXPECTED((delegator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) { /* We must not advance Generator if we yield from a Generator being currently run */ orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; return; @@ -881,12 +883,19 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ generator = zend_generator_get_current(orig_generator); zend_generator_throw_exception(generator, NULL); orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; + delegator = orig_generator; goto try_again; } } /* yield from was used, try another resume. */ - if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM))) { + if (UNEXPECTED(generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM)) { + delegator = generator; + generator = zend_generator_get_current(orig_generator); + goto try_again; + } + if (UNEXPECTED(generator != orig_generator && !Z_ISUNDEF(generator->retval))) { + delegator = orig_generator; generator = zend_generator_get_current(orig_generator); goto try_again; } From 8c90743609a0701bd0d4ed931f6cf6a073a69cf9 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Wed, 12 Aug 2026 06:39:54 -0400 Subject: [PATCH 05/17] Fix GH-17126: get_class_methods omits __invoke for Closure (#21879) Closure::__invoke is documented and reachable via method_exists() and ReflectionClass (getMethod, hasMethod, getMethods), but get_class_methods() walks ce->function_table directly and Closure does not register __invoke there: the parameter list is materialized per-instance by zend_get_closure_invoke_method(). Append __invoke to the result when ce == zend_ce_closure, the same predicate is_closure_invoke() already uses in ext/reflection. Closes GH-17126 --- Zend/tests/get_class_methods/gh17126.phpt | 48 +++++++++++++++++++++++ Zend/zend_builtin_functions.c | 5 +++ 2 files changed, 53 insertions(+) create mode 100644 Zend/tests/get_class_methods/gh17126.phpt diff --git a/Zend/tests/get_class_methods/gh17126.phpt b/Zend/tests/get_class_methods/gh17126.phpt new file mode 100644 index 000000000000..94cc28a8a9b4 --- /dev/null +++ b/Zend/tests/get_class_methods/gh17126.phpt @@ -0,0 +1,48 @@ +--TEST-- +GH-17126 (get_class_methods($closure) doesn't return __invoke) +--FILE-- + "hello {$str}"; + +echo "from object:\n"; +$methods = get_class_methods($closure); +sort($methods); +print_r($methods); + +echo "from class name:\n"; +$methods = get_class_methods('Closure'); +sort($methods); +print_r($methods); + +echo "unrelated class unaffected:\n"; +class NoInvoke { public function foo() {} } +print_r(get_class_methods('NoInvoke')); + +?> +--EXPECT-- +from object: +Array +( + [0] => __invoke + [1] => bind + [2] => bindTo + [3] => call + [4] => fromCallable + [5] => getCurrent +) +from class name: +Array +( + [0] => __invoke + [1] => bind + [2] => bindTo + [3] => call + [4] => fromCallable + [5] => getCurrent +) +unrelated class unaffected: +Array +( + [0] => foo +) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 860413f84111..6aa47abd2c6b 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -957,6 +957,11 @@ ZEND_FUNCTION(get_class_methods) zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name); } } ZEND_HASH_FOREACH_END(); + + if (ce == zend_ce_closure) { + ZVAL_STR_COPY(&method_name, ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE)); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name); + } } /* }}} */ From a1fdc0b934acf539b1d3acdd10a266e5de8dfaa8 Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:17:12 +0700 Subject: [PATCH 06/17] ext/curl: applied fixers to improve test robustness (#23038) --- ext/curl/tests/bug48207.phpt | 8 ++--- ext/curl/tests/bug68089.phpt | 4 +-- ext/curl/tests/bug73147.phpt | 4 +-- ext/curl/tests/bug80121.phpt | 12 +++---- ext/curl/tests/curl_file_upload.phpt | 4 +-- .../tests/curl_getinfo_CURLINFO_CONN_ID.phpt | 1 - .../tests/curl_multi_errno_strerror_001.phpt | 4 +-- .../tests/curl_multi_setopt_basic001.phpt | 4 +-- ext/curl/tests/curl_persistent_share_003.phpt | 4 +-- ext/curl/tests/curl_persistent_share_004.phpt | 4 +-- ext/curl/tests/curl_persistent_share_005.phpt | 4 +-- ext/curl/tests/curl_persistent_share_006.phpt | 4 +-- ext/curl/tests/curl_seekfunction_error.phpt | 16 +++++----- .../curl_setopt_CURLOPT_DEBUGFUNCTION.phpt | 12 +++---- .../curl_setopt_CURLOPT_PREREQFUNCTION.phpt | 20 ++++++------ ext/curl/tests/curl_setopt_basic003.phpt | 4 +-- ext/curl/tests/curl_setopt_error.phpt | 12 +++---- .../tests/curl_setopt_error_nul_byte.phpt | 31 +++++++------------ .../tests/curl_share_errno_strerror_001.phpt | 4 +-- .../tests/curl_share_setopt_basic001.phpt | 4 +-- ext/curl/tests/gh15547.phpt | 8 ++--- 21 files changed, 79 insertions(+), 89 deletions(-) diff --git a/ext/curl/tests/bug48207.phpt b/ext/curl/tests/bug48207.phpt index 6487076e9ee3..68b945f4bf8c 100644 --- a/ext/curl/tests/bug48207.phpt +++ b/ext/curl/tests/bug48207.phpt @@ -45,7 +45,7 @@ foreach ([ try { curl_setopt($ch, $option, $fp); } catch (ValueError $exception) { - echo $exception->getMessage(), "\n"; + echo $exception::class, ': ', $exception->getMessage(), "\n"; } } @@ -54,8 +54,8 @@ is_file($tempfile) and @unlink($tempfile); isset($tempname) and is_file($tempname) and @unlink($tempname); ?> --EXPECT-- -curl_setopt(): The file handle provided for CURLOPT_FILE must be writable -curl_setopt(): The file handle provided for CURLOPT_WRITEHEADER must be writable -curl_setopt(): The file handle provided for CURLOPT_STDERR must be writable +ValueError: curl_setopt(): The file handle provided for CURLOPT_FILE must be writable +ValueError: curl_setopt(): The file handle provided for CURLOPT_WRITEHEADER must be writable +ValueError: curl_setopt(): The file handle provided for CURLOPT_STDERR must be writable Hello World! Hello World! diff --git a/ext/curl/tests/bug68089.phpt b/ext/curl/tests/bug68089.phpt index c175df5248fe..6a909c39f770 100644 --- a/ext/curl/tests/bug68089.phpt +++ b/ext/curl/tests/bug68089.phpt @@ -10,11 +10,11 @@ $ch = curl_init(); try { curl_setopt($ch, CURLOPT_URL, $url); } catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; + echo $exception::class, ': ', $exception->getMessage(), "\n"; } ?> Done --EXPECT-- -curl_setopt(): cURL option CURLOPT_URL must not contain any null bytes +ValueError: curl_setopt(): cURL option CURLOPT_URL must not contain any null bytes Done diff --git a/ext/curl/tests/bug73147.phpt b/ext/curl/tests/bug73147.phpt index b0f01e64e31d..4561785cb999 100644 --- a/ext/curl/tests/bug73147.phpt +++ b/ext/curl/tests/bug73147.phpt @@ -8,8 +8,8 @@ $poc = 'a:1:{i:0;O:8:"CURLFile":1:{s:4:"name";R:1;}}'; try { var_dump(unserialize($poc)); } catch(Exception $e) { - echo $e->getMessage(); + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } ?> --EXPECT-- -Unserialization of 'CURLFile' is not allowed +Exception: Unserialization of 'CURLFile' is not allowed diff --git a/ext/curl/tests/bug80121.phpt b/ext/curl/tests/bug80121.phpt index a7bf6385367d..d750856f06ec 100644 --- a/ext/curl/tests/bug80121.phpt +++ b/ext/curl/tests/bug80121.phpt @@ -8,21 +8,21 @@ curl try { new CurlHandle; } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } try { new CurlMultiHandle; } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } try { new CurlShareHandle; } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> --EXPECT-- -Cannot directly construct CurlHandle, use curl_init() instead -Cannot directly construct CurlMultiHandle, use curl_multi_init() instead -Cannot directly construct CurlShareHandle, use curl_share_init() instead +Error: Cannot directly construct CurlHandle, use curl_init() instead +Error: Cannot directly construct CurlMultiHandle, use curl_multi_init() instead +Error: Cannot directly construct CurlShareHandle, use curl_share_init() instead diff --git a/ext/curl/tests/curl_file_upload.phpt b/ext/curl/tests/curl_file_upload.phpt index 8b85190ff41e..732d626c664b 100644 --- a/ext/curl/tests/curl_file_upload.phpt +++ b/ext/curl/tests/curl_file_upload.phpt @@ -45,7 +45,7 @@ var_dump(curl_exec($ch)); try { curl_setopt($ch, CURLOPT_SAFE_UPLOAD, 0); } catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; + echo $exception::class, ': ', $exception->getMessage(), "\n"; } $params = array('file' => '@' . __DIR__ . '/curl_testdata1.txt'); @@ -72,7 +72,7 @@ string(%d) "%s/curl_testdata1.txt" string(%d) "curl_testdata1.txt|text/plain|6" string(%d) "foo.txt" string(%d) "foo.txt|application/octet-stream|6" -curl_setopt(): Disabling safe uploads is no longer supported +ValueError: curl_setopt(): Disabling safe uploads is no longer supported string(0) "" string(0) "" string(%d) "array(1) { diff --git a/ext/curl/tests/curl_getinfo_CURLINFO_CONN_ID.phpt b/ext/curl/tests/curl_getinfo_CURLINFO_CONN_ID.phpt index 4a90b0a3c52e..be671e73ef3c 100644 --- a/ext/curl/tests/curl_getinfo_CURLINFO_CONN_ID.phpt +++ b/ext/curl/tests/curl_getinfo_CURLINFO_CONN_ID.phpt @@ -143,4 +143,3 @@ bool(true) bool(true) bool(true) bool(true) - diff --git a/ext/curl/tests/curl_multi_errno_strerror_001.phpt b/ext/curl/tests/curl_multi_errno_strerror_001.phpt index 80ec44c4c247..9bb9b168f828 100644 --- a/ext/curl/tests/curl_multi_errno_strerror_001.phpt +++ b/ext/curl/tests/curl_multi_errno_strerror_001.phpt @@ -13,7 +13,7 @@ echo curl_multi_strerror($errno) . PHP_EOL; try { curl_multi_setopt($mh, -1, -1); } catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; + echo $exception::class, ': ', $exception->getMessage(), "\n"; } $errno = curl_multi_errno($mh); @@ -23,6 +23,6 @@ echo curl_multi_strerror($errno) . PHP_EOL; --EXPECT-- 0 No error -curl_multi_setopt(): Argument #2 ($option) is not a valid cURL multi option +ValueError: curl_multi_setopt(): Argument #2 ($option) is not a valid cURL multi option 6 Unknown option diff --git a/ext/curl/tests/curl_multi_setopt_basic001.phpt b/ext/curl/tests/curl_multi_setopt_basic001.phpt index 71d176c0a109..479ae758e0c2 100644 --- a/ext/curl/tests/curl_multi_setopt_basic001.phpt +++ b/ext/curl/tests/curl_multi_setopt_basic001.phpt @@ -11,10 +11,10 @@ var_dump(curl_multi_setopt($mh, CURLMOPT_PIPELINING, 0)); try { curl_multi_setopt($mh, -1, 0); } catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; + echo $exception::class, ': ', $exception->getMessage(), "\n"; } ?> --EXPECT-- bool(true) -curl_multi_setopt(): Argument #2 ($option) is not a valid cURL multi option +ValueError: curl_multi_setopt(): Argument #2 ($option) is not a valid cURL multi option diff --git a/ext/curl/tests/curl_persistent_share_003.phpt b/ext/curl/tests/curl_persistent_share_003.phpt index 60a30de4d464..0ed7dfd22e97 100644 --- a/ext/curl/tests/curl_persistent_share_003.phpt +++ b/ext/curl/tests/curl_persistent_share_003.phpt @@ -8,9 +8,9 @@ curl try { $sh = curl_share_init_persistent([CURL_LOCK_DATA_DNS, CURL_LOCK_DATA_CONNECT, 30]); } catch (\ValueError $e) { - echo $e->getMessage() . PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } ?> --EXPECT-- -curl_share_init_persistent(): Argument #1 ($share_options) must contain only CURL_LOCK_DATA_* constants +ValueError: curl_share_init_persistent(): Argument #1 ($share_options) must contain only CURL_LOCK_DATA_* constants diff --git a/ext/curl/tests/curl_persistent_share_004.phpt b/ext/curl/tests/curl_persistent_share_004.phpt index fe0e7ec2cf0e..ffd58abc0172 100644 --- a/ext/curl/tests/curl_persistent_share_004.phpt +++ b/ext/curl/tests/curl_persistent_share_004.phpt @@ -8,9 +8,9 @@ curl try { $sh = curl_share_init_persistent([CURL_LOCK_DATA_COOKIE]); } catch (\ValueError $e) { - echo $e->getMessage() . PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } ?> --EXPECT-- -curl_share_init_persistent(): Argument #1 ($share_options) must not contain CURL_LOCK_DATA_COOKIE because sharing cookies across PHP requests is unsafe +ValueError: curl_share_init_persistent(): Argument #1 ($share_options) must not contain CURL_LOCK_DATA_COOKIE because sharing cookies across PHP requests is unsafe diff --git a/ext/curl/tests/curl_persistent_share_005.phpt b/ext/curl/tests/curl_persistent_share_005.phpt index 8737ba6c39a4..7b7403a76f3b 100644 --- a/ext/curl/tests/curl_persistent_share_005.phpt +++ b/ext/curl/tests/curl_persistent_share_005.phpt @@ -10,9 +10,9 @@ $sh = curl_share_init_persistent([CURL_LOCK_DATA_DNS]); try { curl_share_setopt($sh, CURLOPT_SHARE, CURL_LOCK_DATA_CONNECT); } catch (\TypeError $e) { - echo $e->getMessage() . PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } ?> --EXPECT-- -curl_share_setopt(): Argument #1 ($share_handle) must be of type CurlShareHandle, CurlSharePersistentHandle given +TypeError: curl_share_setopt(): Argument #1 ($share_handle) must be of type CurlShareHandle, CurlSharePersistentHandle given diff --git a/ext/curl/tests/curl_persistent_share_006.phpt b/ext/curl/tests/curl_persistent_share_006.phpt index ffca764757fb..8921ddba8903 100644 --- a/ext/curl/tests/curl_persistent_share_006.phpt +++ b/ext/curl/tests/curl_persistent_share_006.phpt @@ -8,9 +8,9 @@ curl try { $sh = curl_share_init_persistent([]); } catch (\ValueError $e) { - echo $e->getMessage() . PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } ?> --EXPECT-- -curl_share_init_persistent(): Argument #1 ($share_options) must not be empty +ValueError: curl_share_init_persistent(): Argument #1 ($share_options) must not be empty diff --git a/ext/curl/tests/curl_seekfunction_error.phpt b/ext/curl/tests/curl_seekfunction_error.phpt index 134e8115dc5f..31808d78a3c8 100644 --- a/ext/curl/tests/curl_seekfunction_error.phpt +++ b/ext/curl/tests/curl_seekfunction_error.phpt @@ -31,14 +31,14 @@ echo "Returning a non-int:\n"; try { run_upload($host, fn($ch, $offset, $origin) => 'not an int'); } catch (\TypeError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } echo "\nReturning an out-of-range int:\n"; try { run_upload($host, fn($ch, $offset, $origin) => 42); } catch (\ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } echo "\nThrowing from the callback:\n"; @@ -47,7 +47,7 @@ try { throw new \RuntimeException('boom from seek'); }); } catch (\RuntimeException $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } echo "\nSetting the callback to null:\n"; @@ -57,21 +57,21 @@ echo "\nSetting a non-callable scalar:\n"; try { curl_setopt(curl_init(), CURLOPT_SEEKFUNCTION, 42); } catch (\TypeError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> --EXPECT-- Returning a non-int: -The CURLOPT_SEEKFUNCTION callback must return one of CURL_SEEKFUNC_OK, CURL_SEEKFUNC_FAIL or CURL_SEEKFUNC_CANTSEEK +TypeError: The CURLOPT_SEEKFUNCTION callback must return one of CURL_SEEKFUNC_OK, CURL_SEEKFUNC_FAIL or CURL_SEEKFUNC_CANTSEEK Returning an out-of-range int: -The CURLOPT_SEEKFUNCTION callback must return one of CURL_SEEKFUNC_OK, CURL_SEEKFUNC_FAIL or CURL_SEEKFUNC_CANTSEEK +ValueError: The CURLOPT_SEEKFUNCTION callback must return one of CURL_SEEKFUNC_OK, CURL_SEEKFUNC_FAIL or CURL_SEEKFUNC_CANTSEEK Throwing from the callback: -boom from seek +RuntimeException: boom from seek Setting the callback to null: bool(true) Setting a non-callable scalar: -curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_SEEKFUNCTION, no array or string given +TypeError: curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_SEEKFUNCTION, no array or string given diff --git a/ext/curl/tests/curl_setopt_CURLOPT_DEBUGFUNCTION.phpt b/ext/curl/tests/curl_setopt_CURLOPT_DEBUGFUNCTION.phpt index 7193642dcab0..b523acc1648b 100644 --- a/ext/curl/tests/curl_setopt_CURLOPT_DEBUGFUNCTION.phpt +++ b/ext/curl/tests/curl_setopt_CURLOPT_DEBUGFUNCTION.phpt @@ -87,14 +87,14 @@ try { var_dump(curl_setopt($ch, CURLINFO_HEADER_OUT, true)); } catch (\ValueError $e) { - var_dump($e->getMessage()); + echo $e::class, ': ', $e->getMessage(), "\n"; } $chCopy = curl_copy_handle($ch); try { var_dump(curl_setopt($chCopy, CURLINFO_HEADER_OUT, true)); } catch (\ValueError $e) { - var_dump($e->getMessage()); + echo $e::class, ': ', $e->getMessage(), "\n"; } var_dump(curl_setopt($chCopy, CURLOPT_DEBUGFUNCTION, null)); var_dump(curl_setopt($chCopy, CURLINFO_HEADER_OUT, true)); @@ -138,7 +138,7 @@ try { var_dump($result = curl_exec($ch)); } catch (\RuntimeException $e) { - var_dump($e->getMessage()); + echo $e::class, ': ', $e->getMessage(), "\n"; } var_dump(curl_getinfo($ch, CURLINFO_HEADER_OUT)); @@ -183,8 +183,8 @@ bool(true) bool(true) bool(true) bool(true) -string(87) "CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set" -string(87) "CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set" +ValueError: CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set +ValueError: CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set bool(true) bool(true) @@ -216,7 +216,7 @@ Accept: */* ===Test CURLOPT_DEBUGFUNCTION can throw within callback=== bool(true) -string(41) "This should get caught after verbose=true" +RuntimeException: This should get caught after verbose=true string(%d) "GET /get.inc?test=file HTTP/%s Host: %s:%d Accept: */* diff --git a/ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt b/ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt index 39a1d1e3caa8..8f507a6c8fd9 100644 --- a/ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt +++ b/ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt @@ -73,7 +73,7 @@ curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) { try { curl_exec($ch); } catch (\TypeError $e) { - echo $e->getMessage() . \PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } echo "\nTesting with invalid type\n"; @@ -83,7 +83,7 @@ curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) { try { curl_exec($ch); } catch (\TypeError $e) { - echo $e->getMessage() . \PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } echo "\nTesting with invalid value\n"; @@ -93,21 +93,21 @@ curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) { try { curl_exec($ch); } catch (\ValueError $e) { - echo $e->getMessage() . \PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } echo "\nTesting with invalid option value\n"; try { curl_setopt($ch, CURLOPT_PREREQFUNCTION, 42); } catch (\TypeError $e) { - echo $e->getMessage() . \PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } echo "\nTesting with invalid option callback\n"; try { curl_setopt($ch, CURLOPT_PREREQFUNCTION, 'function_does_not_exist'); } catch (\TypeError $e) { - echo $e->getMessage() . \PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } echo "\nTesting with null as the callback\n"; @@ -161,19 +161,19 @@ string(0) "" int(0) Testing with no return type -The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT +TypeError: The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT Testing with invalid type -The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT +TypeError: The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT Testing with invalid value -The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT +ValueError: The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT Testing with invalid option value -curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, no array or string given +TypeError: curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, no array or string given Testing with invalid option callback -curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, function "function_does_not_exist" not found or invalid function name +TypeError: curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, function "function_does_not_exist" not found or invalid function name Testing with null as the callback bool(true) diff --git a/ext/curl/tests/curl_setopt_basic003.phpt b/ext/curl/tests/curl_setopt_basic003.phpt index 008162c9b474..daeac6b07c14 100644 --- a/ext/curl/tests/curl_setopt_basic003.phpt +++ b/ext/curl/tests/curl_setopt_basic003.phpt @@ -20,7 +20,7 @@ $ch = curl_init(); try { curl_setopt($ch, CURLOPT_HTTPHEADER, 1); } catch (TypeError $exception) { - echo $exception->getMessage() . "\n"; + echo $exception::class, ': ', $exception->getMessage(), "\n"; } $curl_content = curl_exec($ch); @@ -42,6 +42,6 @@ var_dump( $curl_content ); ?> --EXPECT-- *** curl_setopt() call with CURLOPT_HTTPHEADER -curl_setopt(): The CURLOPT_HTTPHEADER option must have an array value +TypeError: curl_setopt(): The CURLOPT_HTTPHEADER option must have an array value bool(false) bool(true) diff --git a/ext/curl/tests/curl_setopt_error.phpt b/ext/curl/tests/curl_setopt_error.phpt index 0979cec0816d..0495d2e56dab 100644 --- a/ext/curl/tests/curl_setopt_error.phpt +++ b/ext/curl/tests/curl_setopt_error.phpt @@ -13,24 +13,24 @@ $ch = curl_init(); try { curl_setopt($ch, '', false); } catch (TypeError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } try { curl_setopt($ch, -10, 0); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } try { curl_setopt($ch, 1000, 0); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> --EXPECT-- *** curl_setopt() call with incorrect parameters -curl_setopt(): Argument #2 ($option) must be of type int, string given -curl_setopt(): Argument #2 ($option) is not a valid cURL option -curl_setopt(): Argument #2 ($option) is not a valid cURL option +TypeError: curl_setopt(): Argument #2 ($option) must be of type int, string given +ValueError: curl_setopt(): Argument #2 ($option) is not a valid cURL option +ValueError: curl_setopt(): Argument #2 ($option) is not a valid cURL option diff --git a/ext/curl/tests/curl_setopt_error_nul_byte.phpt b/ext/curl/tests/curl_setopt_error_nul_byte.phpt index 23be973b82c2..8eae484b51d9 100644 --- a/ext/curl/tests/curl_setopt_error_nul_byte.phpt +++ b/ext/curl/tests/curl_setopt_error_nul_byte.phpt @@ -24,29 +24,20 @@ foreach ($list_options as $option) { try { curl_setopt($ch, constant($option), ["Something: foo\0bar"]); } catch (ValueError $exception) { - echo $option . ": " . $exception->getMessage() . "\n\n"; + echo $option, ': ', $exception::class, ': ', $exception->getMessage(), "\n"; } } $ch = null; ?> --EXPECT-- -CURLOPT_HTTP200ALIASES: curl_setopt(): cURL option CURLOPT_HTTP200ALIASES must not contain any null bytes - -CURLOPT_HTTPHEADER: curl_setopt(): cURL option CURLOPT_HTTPHEADER must not contain any null bytes - -CURLOPT_POSTQUOTE: curl_setopt(): cURL option CURLOPT_POSTQUOTE must not contain any null bytes - -CURLOPT_PREQUOTE: curl_setopt(): cURL option CURLOPT_PREQUOTE must not contain any null bytes - -CURLOPT_QUOTE: curl_setopt(): cURL option CURLOPT_QUOTE must not contain any null bytes - -CURLOPT_TELNETOPTIONS: curl_setopt(): cURL option CURLOPT_TELNETOPTIONS must not contain any null bytes - -CURLOPT_MAIL_RCPT: curl_setopt(): cURL option CURLOPT_MAIL_RCPT must not contain any null bytes - -CURLOPT_RESOLVE: curl_setopt(): cURL option CURLOPT_RESOLVE must not contain any null bytes - -CURLOPT_PROXYHEADER: curl_setopt(): cURL option CURLOPT_PROXYHEADER must not contain any null bytes - -CURLOPT_CONNECT_TO: curl_setopt(): cURL option CURLOPT_CONNECT_TO must not contain any null bytes +CURLOPT_HTTP200ALIASES: ValueError: curl_setopt(): cURL option CURLOPT_HTTP200ALIASES must not contain any null bytes +CURLOPT_HTTPHEADER: ValueError: curl_setopt(): cURL option CURLOPT_HTTPHEADER must not contain any null bytes +CURLOPT_POSTQUOTE: ValueError: curl_setopt(): cURL option CURLOPT_POSTQUOTE must not contain any null bytes +CURLOPT_PREQUOTE: ValueError: curl_setopt(): cURL option CURLOPT_PREQUOTE must not contain any null bytes +CURLOPT_QUOTE: ValueError: curl_setopt(): cURL option CURLOPT_QUOTE must not contain any null bytes +CURLOPT_TELNETOPTIONS: ValueError: curl_setopt(): cURL option CURLOPT_TELNETOPTIONS must not contain any null bytes +CURLOPT_MAIL_RCPT: ValueError: curl_setopt(): cURL option CURLOPT_MAIL_RCPT must not contain any null bytes +CURLOPT_RESOLVE: ValueError: curl_setopt(): cURL option CURLOPT_RESOLVE must not contain any null bytes +CURLOPT_PROXYHEADER: ValueError: curl_setopt(): cURL option CURLOPT_PROXYHEADER must not contain any null bytes +CURLOPT_CONNECT_TO: ValueError: curl_setopt(): cURL option CURLOPT_CONNECT_TO must not contain any null bytes diff --git a/ext/curl/tests/curl_share_errno_strerror_001.phpt b/ext/curl/tests/curl_share_errno_strerror_001.phpt index 6a69a3a48773..835b8a8afef2 100644 --- a/ext/curl/tests/curl_share_errno_strerror_001.phpt +++ b/ext/curl/tests/curl_share_errno_strerror_001.phpt @@ -13,7 +13,7 @@ echo curl_share_strerror($errno) . PHP_EOL; try { curl_share_setopt($sh, -1, -1); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } $errno = curl_share_errno($sh); @@ -23,6 +23,6 @@ echo curl_share_strerror($errno) . PHP_EOL; --EXPECT-- 0 No error -curl_share_setopt(): Argument #2 ($option) is not a valid cURL share option +ValueError: curl_share_setopt(): Argument #2 ($option) is not a valid cURL share option 1 Unknown share option diff --git a/ext/curl/tests/curl_share_setopt_basic001.phpt b/ext/curl/tests/curl_share_setopt_basic001.phpt index d9a9c1788e88..11a5e4d1dc87 100644 --- a/ext/curl/tests/curl_share_setopt_basic001.phpt +++ b/ext/curl/tests/curl_share_setopt_basic001.phpt @@ -12,11 +12,11 @@ var_dump(curl_share_setopt($sh, CURLSHOPT_UNSHARE, CURL_LOCK_DATA_DNS)); try { curl_share_setopt($sh, -1, 0); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> --EXPECT-- bool(true) bool(true) -curl_share_setopt(): Argument #2 ($option) is not a valid cURL share option +ValueError: curl_share_setopt(): Argument #2 ($option) is not a valid cURL share option diff --git a/ext/curl/tests/gh15547.phpt b/ext/curl/tests/gh15547.phpt index 489fdd0b999a..6b2b0521a1c6 100644 --- a/ext/curl/tests/gh15547.phpt +++ b/ext/curl/tests/gh15547.phpt @@ -10,20 +10,20 @@ $mh = curl_multi_init(); try { curl_multi_select($mh, -2500000); } catch (\ValueError $e) { - echo $e->getMessage() . PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } curl_multi_close($mh); $mh = curl_multi_init(); try { curl_multi_select($mh, 2500000); } catch (\ValueError $e) { - echo $e->getMessage() . PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } curl_multi_close($mh); $mh = curl_multi_init(); var_dump(curl_multi_select($mh, 1000000)); ?> --EXPECTF-- -curl_multi_select(): Argument #2 ($timeout) must be between %d and %f -curl_multi_select(): Argument #2 ($timeout) must be between %d and %f +ValueError: curl_multi_select(): Argument #2 ($timeout) must be between %d and %f +ValueError: curl_multi_select(): Argument #2 ($timeout) must be between %d and %f int(0) From 849d955f1e7311e5aa0c8fcebbfc851f4d3c3639 Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:22:18 +0700 Subject: [PATCH 07/17] [skip ci] CODING_STANDARDS.md: added exception class assertion reminder (#23220) Co-authored-by: Weilin Du --- CODING_STANDARDS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md index 3a53d0e258ca..f2c559a389ff 100644 --- a/CODING_STANDARDS.md +++ b/CODING_STANDARDS.md @@ -295,6 +295,21 @@ rewritten to comply with these rules. 1. Extensions should be well tested using `*.phpt` tests. Read more at [qa.php.net](https://qa.php.net/write-test.php) documentation. +2. When testing exceptions, assert the error class in the catch block. + +```diff + try { + throw new ValueError('foo'); + } catch (ValueError $e) { +- echo $e->getMessage(), "\n"; ++ echo $e::class, ': ', $e->getMessage(), "\n"; + } + + --EXPECT-- +- foo ++ ValueError: foo +``` + ## New and experimental functions To reduce the problems normally associated with the first public implementation From f74acb62a39a70849b50621c11f49f8eabd87aa9 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 12 Aug 2026 13:51:03 +0100 Subject: [PATCH 08/17] Zend: remove zend_is_countable() (#23010) This function is only used once internally, and no usage is reported from a SourceGraph search. [1] [1] https://sourcegraph.com/search?q=context:global+-f:zend_API.c+-f:zend_API.h+zend_is_countable&patternType=keyword&sm=0 --- UPGRADING.INTERNALS | 1 + Zend/zend_API.c | 17 ----------------- Zend/zend_API.h | 2 -- ext/standard/type.c | 15 ++++++++++++++- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 4be8421dd7b1..a83b4f882fe2 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -136,6 +136,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES . The zend_save_error_handling() function was removed. . The zend_parse_parameter() function has been removed, use one fo the zend_parse_arg_TYPE() APIs instead. + . The zend_is_countable() function was removed. - Changed: . Internal functions that return by reference are now expected to diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e47c489dcee7..7731f4094ed6 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -5233,23 +5233,6 @@ ZEND_API bool zend_is_iterable(const zval *iterable) /* {{{ */ } /* }}} */ -ZEND_API bool zend_is_countable(const zval *countable) /* {{{ */ -{ - switch (Z_TYPE_P(countable)) { - case IS_ARRAY: - return 1; - case IS_OBJECT: - if (Z_OBJ_HT_P(countable)->count_elements) { - return 1; - } - - return zend_class_implements_interface(Z_OBJCE_P(countable), zend_ce_countable); - default: - return 0; - } -} -/* }}} */ - static zend_result get_default_via_ast(zval *default_value_zval, const char *default_value) { zend_ast *ast; zend_arena *ast_arena; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index ad224884f360..b91d586eedc3 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -960,8 +960,6 @@ static zend_always_inline const char *zend_get_object_type_uc(const zend_class_e ZEND_API bool zend_is_iterable(const zval *iterable); -ZEND_API bool zend_is_countable(const zval *countable); - ZEND_API void zend_convert_internal_arg_info(zend_arg_info *new_arg_info, const zend_internal_arg_info *arg_info, bool is_return_info, bool permanent); diff --git a/ext/standard/type.c b/ext/standard/type.c index fc681a3c50a7..dc99dac93dac 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -13,6 +13,7 @@ */ #include "php.h" +#include "zend_interfaces.h" /* {{{ Returns the type of the variable */ PHP_FUNCTION(gettype) @@ -458,6 +459,18 @@ PHP_FUNCTION(is_countable) Z_PARAM_ZVAL(var) ZEND_PARSE_PARAMETERS_END(); - RETURN_BOOL(zend_is_countable(var)); + + switch (Z_TYPE_P(var)) { + case IS_ARRAY: + RETURN_TRUE; + case IS_OBJECT: + if (Z_OBJ_HT_P(var)->count_elements) { + RETURN_TRUE; + } + + RETURN_BOOL(zend_class_implements_interface(Z_OBJCE_P(var), zend_ce_countable)); + default: + RETURN_FALSE; + } } /* }}} */ From e36a123efbeda2472e890f64db05a713471dee7d Mon Sep 17 00:00:00 2001 From: Yudai Takada Date: Wed, 12 Aug 2026 22:46:32 +0900 Subject: [PATCH 09/17] zend_language_parser: Remove unused backup_lex_pos (GH-23224) --- Zend/zend_language_parser.y | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index b4dda00404ea..2d9d7b4a23d6 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -292,7 +292,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers %type class_modifiers class_modifier anonymous_class_modifiers anonymous_class_modifiers_optional use_type backup_fn_flags -%type backup_lex_pos %type backup_doc_comment %type reserved_non_modifiers semi_reserved @@ -1400,9 +1399,9 @@ inline_function: NULL, $5, $7, $11, $8, NULL); CG(extra_fn_flags) = $9; } | fn returns_ref backup_doc_comment '(' parameter_list ')' return_type - T_DOUBLE_ARROW backup_fn_flags backup_lex_pos expr backup_fn_flags - { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $3, - NULL, $5, NULL, $11, $7, NULL); + T_DOUBLE_ARROW backup_fn_flags expr backup_fn_flags + { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $11, $1, $3, + NULL, $5, NULL, $10, $7, NULL); CG(extra_fn_flags) = $9; } ; @@ -1422,10 +1421,6 @@ backup_fn_flags: %prec PREC_ARROW_FUNCTION %empty { $$ = CG(extra_fn_flags); CG(extra_fn_flags) = 0; } ; -backup_lex_pos: - %empty { $$ = LANG_SCNG(yy_text); } -; - returns_ref: %empty { $$ = 0; } | ampersand { $$ = ZEND_ACC_RETURN_REFERENCE; } From 89a17252a495202895a219ff93c1a08d3acee863 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 10 Jul 2026 12:32:07 +0200 Subject: [PATCH 10/17] Make strtolower()/strtoupper() frameless Both are single-argument wrappers around zend_string_tolower/upper; static analyzers and general string handling call them at very high frequency. The frameless call convention removes the call frame setup and argument copying. Co-authored-by: Markus Staab Closes GH-23084 --- ext/standard/basic_functions.stub.php | 10 ++++++++-- ext/standard/basic_functions_arginfo.h | 18 +++++++++++++++--- ext/standard/basic_functions_decl.h | 8 ++++---- ext/standard/string.c | 26 ++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 9f52df10e1d3..c87e22433400 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2368,10 +2368,16 @@ function join(string|array $separator, ?array $array = null): string {} */ function strtok(string $string, ?string $token = null): string|false {} -/** @compile-time-eval */ +/** + * @compile-time-eval + * @frameless-function {"arity": 1} + */ function strtoupper(string $string): string {} -/** @compile-time-eval */ +/** + * @compile-time-eval + * @frameless-function {"arity": 1} + */ function strtolower(string $string): string {} function str_increment(string $string): string {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 0155187f1616..ab8c57d4e8d9 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: 8f5682d85611126ee12182af46cb2905a0bd0e57 + * Stub hash: cf2ea35cd867f7091ee1fcf83bbfb543b51e5786 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) @@ -2246,6 +2246,18 @@ static const zend_frameless_function_info frameless_function_infos_implode[] = { { 0 }, }; +ZEND_FRAMELESS_FUNCTION(strtoupper, 1); +static const zend_frameless_function_info frameless_function_infos_strtoupper[] = { + { ZEND_FRAMELESS_FUNCTION_NAME(strtoupper, 1), 1 }, + { 0 }, +}; + +ZEND_FRAMELESS_FUNCTION(strtolower, 1); +static const zend_frameless_function_info frameless_function_infos_strtolower[] = { + { ZEND_FRAMELESS_FUNCTION_NAME(strtolower, 1), 1 }, + { 0 }, +}; + ZEND_FRAMELESS_FUNCTION(dirname, 1); ZEND_FRAMELESS_FUNCTION(dirname, 2); static const zend_frameless_function_info frameless_function_infos_dirname[] = { @@ -3168,8 +3180,8 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY("implode", zif_implode, arginfo_implode, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_implode, NULL) ZEND_RAW_FENTRY("join", zif_implode, arginfo_join, 0, NULL, NULL) ZEND_FE(strtok, arginfo_strtok) - ZEND_RAW_FENTRY("strtoupper", zif_strtoupper, arginfo_strtoupper, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) - ZEND_RAW_FENTRY("strtolower", zif_strtolower, arginfo_strtolower, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("strtoupper", zif_strtoupper, arginfo_strtoupper, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_strtoupper, NULL) + ZEND_RAW_FENTRY("strtolower", zif_strtolower, arginfo_strtolower, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_strtolower, NULL) ZEND_FE(str_increment, arginfo_str_increment) ZEND_FE(str_decrement, arginfo_str_decrement) ZEND_FE(basename, arginfo_basename) diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h index 40e4c918ba60..02203be0df41 100644 --- a/ext/standard/basic_functions_decl.h +++ b/ext/standard/basic_functions_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: 8f5682d85611126ee12182af46cb2905a0bd0e57 */ + * Stub hash: cf2ea35cd867f7091ee1fcf83bbfb543b51e5786 */ -#ifndef ZEND_BASIC_FUNCTIONS_DECL_8f5682d85611126ee12182af46cb2905a0bd0e57_H -#define ZEND_BASIC_FUNCTIONS_DECL_8f5682d85611126ee12182af46cb2905a0bd0e57_H +#ifndef ZEND_BASIC_FUNCTIONS_DECL_cf2ea35cd867f7091ee1fcf83bbfb543b51e5786_H +#define ZEND_BASIC_FUNCTIONS_DECL_cf2ea35cd867f7091ee1fcf83bbfb543b51e5786_H typedef enum zend_enum_SortDirection { ZEND_ENUM_SortDirection_Ascending = 1, @@ -20,4 +20,4 @@ typedef enum zend_enum_RoundingMode { ZEND_ENUM_RoundingMode_PositiveInfinity = 8, } zend_enum_RoundingMode; -#endif /* ZEND_BASIC_FUNCTIONS_DECL_8f5682d85611126ee12182af46cb2905a0bd0e57_H */ +#endif /* ZEND_BASIC_FUNCTIONS_DECL_cf2ea35cd867f7091ee1fcf83bbfb543b51e5786_H */ diff --git a/ext/standard/string.c b/ext/standard/string.c index 91c930515b78..e5307a4f2d4b 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1192,6 +1192,19 @@ PHP_FUNCTION(strtoupper) } /* }}} */ +ZEND_FRAMELESS_FUNCTION(strtoupper, 1) +{ + zval str_tmp; + zend_string *str; + + Z_FLF_PARAM_STR(1, str, str_tmp); + + RETVAL_STR(zend_string_toupper(str)); + +flf_clean: + Z_FLF_PARAM_FREE_STR(1, str_tmp); +} + /* {{{ Makes a string lowercase */ PHP_FUNCTION(strtolower) { @@ -1205,6 +1218,19 @@ PHP_FUNCTION(strtolower) } /* }}} */ +ZEND_FRAMELESS_FUNCTION(strtolower, 1) +{ + zval str_tmp; + zend_string *str; + + Z_FLF_PARAM_STR(1, str, str_tmp); + + RETVAL_STR(zend_string_tolower(str)); + +flf_clean: + Z_FLF_PARAM_FREE_STR(1, str_tmp); +} + PHP_FUNCTION(str_increment) { zend_string *str; From f6dba91e6219ecb77018569f9800eca6edebb57f Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 12 Aug 2026 22:04:57 +0800 Subject: [PATCH 11/17] ext/readline: Fix class constant completion in readline interactive shell (#23218) Reference: https://github.com/php/php-src/pull/22994#discussion_r3708633591_ php -a class constant completion passed the full ClassName::PREFIX string to the constant completion generator. And textlen had already been shortened to the part after "::". This commit fixes this bug. Co-authored-by: Ilia Alshanetsky --- NEWS | 3 ++ ext/readline/readline_cli.c | 4 ++- ...eadline_cli_completion_class_constant.phpt | 30 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 ext/readline/tests/readline_cli_completion_class_constant.phpt diff --git a/NEWS b/NEWS index ffefe0d338ec..0d9e42792078 100644 --- a/NEWS +++ b/NEWS @@ -60,6 +60,9 @@ PHP NEWS . Fixed segfault in ReflectionMethod::createFromMethodName() on an uninstantiable subclass. (iliaal) +- Readline: + . Fixed class constant completion in the interactive shell. (Weilin Du) + - Session: . Fix corruption in mod_mm. (ndossche) . Fixed bug GH-23043 (broken session id code can cause zend_mm_heap diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c index ca4e8eb4fe9f..ff5caee9eb7d 100644 --- a/ext/readline/readline_cli.c +++ b/ext/readline/readline_cli.c @@ -531,6 +531,7 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */ } else { char *lc_text; const char *class_name_end; + const char *constant_text = text; zend_string *class_name = NULL; zend_class_entry *ce = NULL; @@ -543,6 +544,7 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */ zend_string_release_ex(class_name, 0); return NULL; } + constant_text = class_name_end + 2; lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len); textlen -= (class_name_len + 2); } else { @@ -559,7 +561,7 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */ ZEND_FALLTHROUGH; case 2: case 3: - retval = cli_completion_generator_define(text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants)); + retval = cli_completion_generator_define(constant_text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants)); if (retval || ce) { break; } diff --git a/ext/readline/tests/readline_cli_completion_class_constant.phpt b/ext/readline/tests/readline_cli_completion_class_constant.phpt new file mode 100644 index 000000000000..1d4e6228df01 --- /dev/null +++ b/ext/readline/tests/readline_cli_completion_class_constant.phpt @@ -0,0 +1,30 @@ +--TEST-- +Interactive shell: class constant completion +--EXTENSIONS-- +readline +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +%AInteractive shell%AZED_OK%A From fde23dffbcb15b2d82d00d11470f1d000ce446ec Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 12 Aug 2026 15:37:02 +0200 Subject: [PATCH 12/17] Revert lineno changes Partially reverts: - 07d308a0debc1219d4ef4b789d6a46ec4fb740d3 - 7ad79be6550fba20df3d7cea0a5d9da79861c1f9 We'll solve this in a different way. --- NEWS | 2 - Zend/zend_compile.c | 46 ++++++---------------- ext/opcache/tests/gh18985.phpt | 2 + ext/opcache/tests/jit/shift_right_004.phpt | 4 +- ext/opcache/tests/jit/switch_001.phpt | 4 +- 5 files changed, 18 insertions(+), 40 deletions(-) diff --git a/NEWS b/NEWS index e569af4b6a0b..fa2bc69c39f7 100644 --- a/NEWS +++ b/NEWS @@ -192,8 +192,6 @@ PHP NEWS . Implemented partial function application RFC. (Arnaud) . Fixed bug GH-22263 (reset typed property default on every unserialize failure path). (David Carlier) - . Fixed bug GH-18985 (Wrong line numbers for match with constant arms). - (ilutov) . Fixed bug GH-18847 (SEGV in zend_fetch_debug_backtrace() when the memory limit is reached while the tracing JIT enters a call frame). (Arnaud, iliaal) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1d837c59832b..91822e886684 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7165,8 +7165,6 @@ static void zend_compile_match(znode *result, zend_ast *ast) zend_ast *arm_ast = arms->child[i]; zend_ast *body_ast = arm_ast->child[1]; - CG(zend_lineno) = zend_ast_get_lineno(arm_ast); - if (arm_ast->child[0] != NULL) { zend_ast_list *conds = zend_ast_get_list(arm_ast->child[0]); @@ -10787,8 +10785,6 @@ static void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */ zend_compile_expr(&left_node, left_ast); zend_compile_expr(&right_node, right_ast); - CG(zend_lineno) = ast->lineno; - if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) { if (zend_try_ct_eval_binary_op(&result->u.constant, opcode, &left_node.u.constant, &right_node.u.constant) @@ -12290,7 +12286,7 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ return; } - CG(zend_lineno) = zend_ast_get_lineno(ast); + CG(zend_lineno) = ast->lineno; if ((CG(compiler_options) & ZEND_COMPILE_EXTENDED_STMT) && !zend_is_unticked_stmt(ast)) { zend_do_extended_stmt(NULL); @@ -12414,6 +12410,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ { + /* CG(zend_lineno) = ast->lineno; */ + CG(zend_lineno) = zend_ast_get_lineno(ast); + if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) { zend_compile_memoized_expr(result, ast, BP_VAR_R); return; @@ -12555,9 +12554,6 @@ static void zend_compile_expr(znode *result, zend_ast *ast) { zend_check_stack_limit(); - uint32_t prev_lineno = CG(zend_lineno); - CG(zend_lineno) = zend_ast_get_lineno(ast); - uint32_t checkpoint = zend_short_circuiting_checkpoint(); zend_compile_expr_inner(result, ast); zend_short_circuiting_commit(checkpoint, result, ast); @@ -12567,12 +12563,12 @@ static void zend_compile_expr(znode *result, zend_ast *ast) ZEND_ASSERT(result->op_type != IS_VAR); } #endif - - CG(zend_lineno) = prev_lineno; } static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t type, bool by_ref) { + CG(zend_lineno) = zend_ast_get_lineno(ast); + if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) { switch (ast->kind) { case ZEND_AST_CALL: @@ -12633,9 +12629,6 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo { zend_check_stack_limit(); - uint32_t prev_lineno = CG(zend_lineno); - CG(zend_lineno) = zend_ast_get_lineno(ast); - uint32_t checkpoint = zend_short_circuiting_checkpoint(); zend_op *opcode = zend_compile_var_inner(result, ast, type, by_ref); zend_short_circuiting_commit(checkpoint, result, ast); @@ -12649,9 +12642,6 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo ZEND_ASSERT(result->op_type != IS_VAR); } #endif - - CG(zend_lineno) = prev_lineno; - return opcode; } @@ -12659,37 +12649,25 @@ static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t { zend_check_stack_limit(); - uint32_t prev_lineno = CG(zend_lineno); - CG(zend_lineno) = zend_ast_get_lineno(ast); - - zend_op *opline; switch (ast->kind) { case ZEND_AST_VAR: - opline = zend_compile_simple_var(result, ast, type, true); - break; + return zend_compile_simple_var(result, ast, type, true); case ZEND_AST_DIM: - opline = zend_delayed_compile_dim(result, ast, type, by_ref); - break; + return zend_delayed_compile_dim(result, ast, type, by_ref); case ZEND_AST_PROP: case ZEND_AST_NULLSAFE_PROP: { - opline = zend_delayed_compile_prop(result, ast, type); + zend_op *opline = zend_delayed_compile_prop(result, ast, type); if (by_ref) { opline->extended_value |= ZEND_FETCH_REF; } - break; + return opline; } case ZEND_AST_STATIC_PROP: - opline = zend_compile_static_prop(result, ast, type, by_ref, true); - break; + return zend_compile_static_prop(result, ast, type, by_ref, true); default: - opline = zend_compile_var(result, ast, type, false); - break; + return zend_compile_var(result, ast, type, false); } - - CG(zend_lineno) = prev_lineno; - - return opline; } /* }}} */ diff --git a/ext/opcache/tests/gh18985.phpt b/ext/opcache/tests/gh18985.phpt index 22c7783cf777..754a0b7dac24 100644 --- a/ext/opcache/tests/gh18985.phpt +++ b/ext/opcache/tests/gh18985.phpt @@ -2,6 +2,8 @@ GH-18985: Wrong lineno for multiline expressions --EXTENSIONS-- opcache +--XFAIL-- +GH-22833 was reverted, so this will need a different fix. --INI-- opcache.enable_cli=1 opcache.opt_debug_level=0x40010000 diff --git a/ext/opcache/tests/jit/shift_right_004.phpt b/ext/opcache/tests/jit/shift_right_004.phpt index 5b816893c53f..df65b747ca4d 100644 --- a/ext/opcache/tests/jit/shift_right_004.phpt +++ b/ext/opcache/tests/jit/shift_right_004.phpt @@ -30,9 +30,9 @@ Warning: Undefined array key 0 in %sshift_right_004.php on line 7 Deprecated: Implicit conversion from float %f to int loses precision in %sshift_right_004.php on line 8 -Warning: A non-numeric value encountered in %sshift_right_004.php on line 6 +Warning: A non-numeric value encountered in %sshift_right_004.php on line 7 -Warning: A non-numeric value encountered in %sshift_right_004.php on line 6 +Warning: A non-numeric value encountered in %sshift_right_004.php on line 7 Fatal error: Uncaught ArithmeticError: Bit shift by negative number in %sshift_right_004.php:8 Stack trace: diff --git a/ext/opcache/tests/jit/switch_001.phpt b/ext/opcache/tests/jit/switch_001.phpt index 898ebb363f21..57ee3a40b840 100644 --- a/ext/opcache/tests/jit/switch_001.phpt +++ b/ext/opcache/tests/jit/switch_001.phpt @@ -16,7 +16,7 @@ foo(); ?> DONE --EXPECTF-- -Warning: Undefined variable $y in %sswitch_001.php on line 3 +Warning: Undefined variable $y in %sswitch_001.php on line 4 -Warning: Undefined variable $y in %sswitch_001.php on line 3 +Warning: Undefined variable $y in %sswitch_001.php on line 5 DONE From 70680ba6497851878ce86ef5714111efa0b57650 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 12 Aug 2026 22:35:07 +0800 Subject: [PATCH 13/17] [skip ci] Fix NEWS entries in #23218 --- NEWS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 0d9e42792078..8cd1640765e7 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,9 @@ PHP NEWS . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or next() call on the inner generator). (iliaal) +- Readline: + . Fixed class constant completion in the interactive shell. (Weilin Du) + 27 Aug 2026, PHP 8.4.25 - Core: @@ -60,9 +63,6 @@ PHP NEWS . Fixed segfault in ReflectionMethod::createFromMethodName() on an uninstantiable subclass. (iliaal) -- Readline: - . Fixed class constant completion in the interactive shell. (Weilin Du) - - Session: . Fix corruption in mod_mm. (ndossche) . Fixed bug GH-23043 (broken session id code can cause zend_mm_heap From e213e7f2668deb2b1440669e2c88107b526ce3bf Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 13 Aug 2026 00:27:32 +0800 Subject: [PATCH 14/17] ext/snmp: Add XFAIL on several tests (#22476) (#23231) SNMP tests are likely to fail on Windows CI when network features are tested. So I add all these XFAIL sections to those tests to make sure it don't bother unrelated CI runs. (cherry picked from commit f619e66781d85ad06cc847a0dd5776127a8359bc) --- ext/snmp/tests/bug64124.phpt | 2 ++ ext/snmp/tests/bug64159.phpt | 2 ++ ext/snmp/tests/generic_timeout_error.phpt | 2 ++ ext/snmp/tests/ipv6.phpt | 2 ++ ext/snmp/tests/snmp-object-errno-errstr.phpt | 2 ++ ext/snmp/tests/snmp-object.phpt | 2 ++ ext/snmp/tests/snmp2_get.phpt | 2 ++ ext/snmp/tests/snmp2_getnext.phpt | 2 ++ ext/snmp/tests/snmp2_real_walk.phpt | 2 ++ ext/snmp/tests/snmp2_set-nomib.phpt | 2 ++ ext/snmp/tests/snmp2_set.phpt | 2 ++ ext/snmp/tests/snmp2_walk.phpt | 2 ++ ext/snmp/tests/snmp3-error.phpt | 2 ++ ext/snmp/tests/snmp3.phpt | 2 ++ ext/snmp/tests/snmp_getvalue.phpt | 3 ++- ext/snmp/tests/snmpget.phpt | 2 ++ ext/snmp/tests/snmpgetnext.phpt | 2 ++ ext/snmp/tests/snmprealwalk.phpt | 2 ++ ext/snmp/tests/snmpset-nomib.phpt | 2 ++ ext/snmp/tests/snmpset.phpt | 2 ++ ext/snmp/tests/snmpwalk.phpt | 2 ++ 21 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ext/snmp/tests/bug64124.phpt b/ext/snmp/tests/bug64124.phpt index 4fbf80f0400a..5e2c43dc6ada 100644 --- a/ext/snmp/tests/bug64124.phpt +++ b/ext/snmp/tests/bug64124.phpt @@ -13,6 +13,8 @@ if (@inet_ntop($packed) === false) { die("skip no IPv6 support"); } ?> +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- --ENV-- MIBS=noneXistent +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- --ENV-- MIBS= +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- --ENV-- MIBS= +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- +--XFAIL-- +SNMP tests might possibly fail on Windows --FILE-- Date: Tue, 11 Aug 2026 14:40:47 -0700 Subject: [PATCH 15/17] [RFC] Deprecate `ReflectionProperty::setValue()` with wrong types https://wiki.php.net/rfc/deprecations_php_8_6 --- UPGRADING | 5 +++++ ext/reflection/php_reflection.c | 9 +++++++++ .../tests/ReflectionProperty_setValue_error.phpt | 4 +++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/UPGRADING b/UPGRADING index f82aad3f8b3c..34e3f62c5214 100644 --- a/UPGRADING +++ b/UPGRADING @@ -476,6 +476,11 @@ PHP 8.6 UPGRADE NOTES . The mysqli_get_charset() function is now deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_mysqli_get_charset +- Reflection: + . Calling ReflectionProperty::setValue() with an object that is not an + instance of the class on which the property was declared is now deprecated. + RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_reflectionpropertysetvalue_and_reflectionpropertysetrawvalue_with_wrong_types + - SPL: . The spl_classes() function is now deprecated, use ReflectionExtension::getClassNames() instead. diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 0b1c458db080..90fa559794c1 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5919,6 +5919,15 @@ ZEND_METHOD(ReflectionProperty, setValue) Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END(); + if (!instanceof_function(object->ce, intern->ce)) { + zend_string *method_name = get_active_function_or_method_name(); + zend_error(E_DEPRECATED, "Calling %pS() with a given object that is not an instance of the class this property was declared in is deprecated", method_name); + zend_string_release(method_name); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + } + const zend_class_entry *old_scope = EG(fake_scope); EG(fake_scope) = intern->ce; object->handlers->write_property(object, ref->unmangled_name, value, ref->cache_slot); diff --git a/ext/reflection/tests/ReflectionProperty_setValue_error.phpt b/ext/reflection/tests/ReflectionProperty_setValue_error.phpt index 287a03679e61..25dd9adae2a9 100644 --- a/ext/reflection/tests/ReflectionProperty_setValue_error.phpt +++ b/ext/reflection/tests/ReflectionProperty_setValue_error.phpt @@ -30,11 +30,13 @@ $propInfo = new ReflectionProperty('TestClass', 'pub2'); var_dump($propInfo->setValue($instanceWithNoProperties, "NewValue")); var_dump($instanceWithNoProperties->pub2); ?> ---EXPECT-- +--EXPECTF-- Protected property: string(8) "NewValue" Instance without property: + +Deprecated: Calling ReflectionProperty::setValue() with a given object that is not an instance of the class this property was declared in is deprecated in %s on line %d NULL string(8) "NewValue" From 1b5de53960ab99b288bae2d92748a68a95bff99a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 11 Aug 2026 15:00:18 -0700 Subject: [PATCH 16/17] [RFC] Deprecate `ReflectionProperty::setRawValue()` with wrong types https://wiki.php.net/rfc/deprecations_php_8_6 --- UPGRADING | 3 ++ ext/reflection/php_reflection.c | 9 ++++ .../ReflectionProperty_setRawValue_error.phpt | 50 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 ext/reflection/tests/ReflectionProperty_setRawValue_error.phpt diff --git a/UPGRADING b/UPGRADING index 34e3f62c5214..bfc23acb0fab 100644 --- a/UPGRADING +++ b/UPGRADING @@ -480,6 +480,9 @@ PHP 8.6 UPGRADE NOTES . Calling ReflectionProperty::setValue() with an object that is not an instance of the class on which the property was declared is now deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_reflectionpropertysetvalue_and_reflectionpropertysetrawvalue_with_wrong_types + . Calling ReflectionProperty::setRawValue() with an object that is not an + instance of the class on which the property was declared is now deprecated. + RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_reflectionpropertysetvalue_and_reflectionpropertysetrawvalue_with_wrong_types - SPL: . The spl_classes() function is now deprecated, use diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 90fa559794c1..426ac625a9fc 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6055,6 +6055,15 @@ ZEND_METHOD(ReflectionProperty, setRawValue) Z_PARAM_ZVAL(value) } ZEND_PARSE_PARAMETERS_END(); + if (!instanceof_function(Z_OBJCE_P(object), intern->ce)) { + zend_string *method_name = get_active_function_or_method_name(); + zend_error(E_DEPRECATED, "Calling %pS() with a given object that is not an instance of the class this property was declared in is deprecated", method_name); + zend_string_release(method_name); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + } + zend_reflection_property_set_raw_value(ref->prop, ref->unmangled_name, ref->cache_slot, intern->ce, Z_OBJ_P(object), value); } diff --git a/ext/reflection/tests/ReflectionProperty_setRawValue_error.phpt b/ext/reflection/tests/ReflectionProperty_setRawValue_error.phpt new file mode 100644 index 000000000000..0044e447b764 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_setRawValue_error.phpt @@ -0,0 +1,50 @@ +--TEST-- +Test ReflectionProperty::setRawValue() error cases. +--FILE-- +hooked = "Not virtual"; + } + } +} + +#[AllowDynamicProperties] +class AnotherClass { +} + +$hookedProp = new ReflectionProperty(Example::class, 'hooked'); +$nonHookedProp = new ReflectionProperty(Example::class, 'nonHooked'); + +$instance = new Example(); +$hookedProp->setRawValue($instance, "value1"); +$nonHookedProp->setRawValue($instance, "value2"); +var_dump($instance); + +$other = new AnotherClass(); +$hookedProp->setRawValue($other, "value1"); +$nonHookedProp->setRawValue($other, "value2"); +var_dump($other); +?> +--EXPECTF-- +object(Example)#%d (2) { + ["nonHooked"]=> + string(6) "value2" + ["hooked"]=> + string(6) "value1" +} + +Deprecated: Calling ReflectionProperty::setRawValue() with a given object that is not an instance of the class this property was declared in is deprecated in %s on line %d + +Deprecated: Calling ReflectionProperty::setRawValue() with a given object that is not an instance of the class this property was declared in is deprecated in %s on line %d +object(AnotherClass)#%d (2) { + ["hooked"]=> + string(6) "value1" + ["nonHooked"]=> + string(6) "value2" +} From d3f4eada1001627df6330e7606081e7ab42ca311 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 11 Aug 2026 15:15:37 -0700 Subject: [PATCH 17/17] [RFC] Deprecate `ReflectionMethod` invocation for static methods with objects `ReflectionMethod::invoke()` and `::invokeArgs()` are affected, but share implementation code and so are updated together. https://wiki.php.net/rfc/deprecations_php_8_6 --- UPGRADING | 3 +++ ext/reflection/php_reflection.c | 16 +++++++++++++++- .../ReflectionMethod_invokeArgs_error3.phpt | 4 +++- .../tests/ReflectionMethod_invoke_basic.phpt | 2 ++ .../tests/ReflectionMethod_invoke_error1.phpt | 4 +++- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/UPGRADING b/UPGRADING index bfc23acb0fab..a4cc44f9a004 100644 --- a/UPGRADING +++ b/UPGRADING @@ -483,6 +483,9 @@ PHP 8.6 UPGRADE NOTES . Calling ReflectionProperty::setRawValue() with an object that is not an instance of the class on which the property was declared is now deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_reflectionpropertysetvalue_and_reflectionpropertysetrawvalue_with_wrong_types + . Calling ReflectionMethod::invoke() or ReflectionMethod::invokeArgs() with + an object and a static method is now deprecated. + RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_reflectionmethodinvoke_and_reflectionmethodinvokeargs_with_objects_for_static_methods - SPL: . The spl_classes() function is now deprecated, use diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 426ac625a9fc..07a671739f32 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3375,7 +3375,21 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, bool variadic * Else, we verify that the given object is an instance of the class. */ if (mptr->common.fn_flags & ZEND_ACC_STATIC) { - object = NULL; + if (object) { + zend_string *method_name = get_active_function_or_method_name(); + zend_error( + E_DEPRECATED, + "Calling %pS() for static method %pS::%pS() does not need an object parameter", + method_name, + mptr->common.scope->name, + mptr->common.function_name + ); + zend_string_release(method_name); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + object = NULL; + } obj_ce = mptr->common.scope; } else { if (!object) { diff --git a/ext/reflection/tests/ReflectionMethod_invokeArgs_error3.phpt b/ext/reflection/tests/ReflectionMethod_invokeArgs_error3.phpt index 124f728052e2..57f4702e07c0 100644 --- a/ext/reflection/tests/ReflectionMethod_invokeArgs_error3.phpt +++ b/ext/reflection/tests/ReflectionMethod_invokeArgs_error3.phpt @@ -65,7 +65,7 @@ try { } ?> ---EXPECT-- +--EXPECTF-- Non-instance: string(72) "Given object is not an instance of the class this method was declared in" @@ -75,6 +75,8 @@ Exception: Using $this when not in object context NULL Private method: + +Deprecated: Calling ReflectionMethod::invokeArgs() for static method TestClass::privateMethod() does not need an object parameter in %s on line %d Called privateMethod() NULL diff --git a/ext/reflection/tests/ReflectionMethod_invoke_basic.phpt b/ext/reflection/tests/ReflectionMethod_invoke_basic.phpt index 66f3c50da027..2d35451fa94e 100644 --- a/ext/reflection/tests/ReflectionMethod_invoke_basic.phpt +++ b/ext/reflection/tests/ReflectionMethod_invoke_basic.phpt @@ -104,6 +104,8 @@ NULL Static method: ReflectionMethod::invoke() expects at least 1 argument, 0 given ReflectionMethod::invoke(): Argument #1 ($object) must be of type ?object, true given + +Deprecated: Calling ReflectionMethod::invoke() for static method TestClass::staticMethod() does not need an object parameter in %s on line %d Called staticMethod() Exception: Using $this when not in object context NULL diff --git a/ext/reflection/tests/ReflectionMethod_invoke_error1.phpt b/ext/reflection/tests/ReflectionMethod_invoke_error1.phpt index 1ddf1c51c139..dd86baa91040 100644 --- a/ext/reflection/tests/ReflectionMethod_invoke_error1.phpt +++ b/ext/reflection/tests/ReflectionMethod_invoke_error1.phpt @@ -53,7 +53,7 @@ try { } ?> ---EXPECT-- +--EXPECTF-- invoke() on a non-object: string(85) "ReflectionMethod::invoke(): Argument #1 ($object) must be of type ?object, true given" @@ -61,6 +61,8 @@ invoke() on a non-instance: string(72) "Given object is not an instance of the class this method was declared in" Private method: + +Deprecated: Calling ReflectionMethod::invoke() for static method TestClass::privateMethod() does not need an object parameter in %s on line %d Called privateMethod() NULL