From e7eebb9b1e834971bedf7bfbd7a19e1680e1ee7a Mon Sep 17 00:00:00 2001 From: Jorg Adam Sowa Date: Wed, 8 Jul 2026 16:40:23 +0200 Subject: [PATCH 01/12] sapi/phpdbg: remove dead str_type assignments before goto (#22600) The switch in phpdbg_print_breakpoints() assigned str_type per case and jumped to a shared label that immediately recomputed the same value from brake->type, making the pre-goto assignments dead. Set str_type directly per case instead and drop the goto. --- sapi/phpdbg/phpdbg_bp.c | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/sapi/phpdbg/phpdbg_bp.c b/sapi/phpdbg/phpdbg_bp.c index f4e4ef81af2f..1233f0430121 100644 --- a/sapi/phpdbg/phpdbg_bp.c +++ b/sapi/phpdbg/phpdbg_bp.c @@ -1500,33 +1500,24 @@ PHPDBG_API void phpdbg_print_breakpoints(zend_ulong type) /* {{{ */ switch (brake->type) { case PHPDBG_BREAK_METHOD_OPLINE: str_type = "method"; - goto print_opline; + break; case PHPDBG_BREAK_FUNCTION_OPLINE: str_type = "function"; - goto print_opline; + break; case PHPDBG_BREAK_FILE_OPLINE: - str_type = "method"; - - print_opline: { - if (brake->type == PHPDBG_BREAK_METHOD_OPLINE) { - str_type = "method"; - } else if (brake->type == PHPDBG_BREAK_FUNCTION_OPLINE) { - str_type = "function"; - } else if (brake->type == PHPDBG_BREAK_FILE_OPLINE) { - str_type = "file"; - } - - phpdbg_writeln("#%d\t\t#"ZEND_ULONG_FMT"\t\t(%s breakpoint)%s", - brake->id, brake->opline, str_type, - ((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : ""); - } break; + str_type = "file"; + break; default: phpdbg_writeln("#%d\t\t#"ZEND_ULONG_FMT"%s", brake->id, brake->opline, ((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : ""); - break; + continue; } + + phpdbg_writeln("#%d\t\t#"ZEND_ULONG_FMT"\t\t(%s breakpoint)%s", + brake->id, brake->opline, str_type, + ((phpdbg_breakbase_t *) brake)->disabled ? " [disabled]" : ""); } ZEND_HASH_FOREACH_END(); } break; From 0b9644161c5ff23adda2ee28af86ae11307cbc2b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 8 Jul 2026 10:41:21 -0400 Subject: [PATCH 02/12] ext/iconv/tests/bug76249.phpt: fallback for non-GNU iconv() (#22552) This test fails on musl, where iconv trips over the //IGNORE suffix. It is not obvious that //IGNORE is required to trigger the issue in the first place, but since we are ensuring that a security bug is fixed, it is better to include it where possible. This commit updates the test to append //IGNORE only on the two GNU iconv implementations that are known to support it. POSIX specifies its behavior, but leaves overall support optional. --- ext/iconv/tests/bug76249.phpt | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ext/iconv/tests/bug76249.phpt b/ext/iconv/tests/bug76249.phpt index 37608ccc0447..2fa9b8cc0500 100644 --- a/ext/iconv/tests/bug76249.phpt +++ b/ext/iconv/tests/bug76249.phpt @@ -4,16 +4,27 @@ Bug #76249 (stream filter convert.iconv leads to infinite loop on invalid sequen iconv --FILE-- DONE ---EXPECTF-- -Warning: stream_get_contents(): iconv stream filter ("ucs-2"=>"utf%A8//IGNORE"): invalid multibyte sequence in %sbug76249.php on line %d -string(0) "" +--EXPECTREGEX-- +Warning: stream_get_contents\(\): iconv stream filter \("ucs-2"=>"utf-?8(\/\/IGNORE)?"\): invalid multibyte sequence in .*bug76249\.php on line \d+ +string\(0\) "" DONE From 215ddb732e1481a43a77d5815014a32e2c76751a Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 8 Jul 2026 10:41:58 -0400 Subject: [PATCH 03/12] ext/iconv/tests/bug52211.phpt: use per-iconv charset names (#22543) The charset names used in this test are implementation-specific, and in particular are not known to musl. This causes musl to fall back to utf8, in which the input string is not actually invalid, leading to a failed test. The input string is already invalid in ASCII however, so we solve the general problem by using ASCII as the to/from charset unless the ICONV_IMPL is known to support the originals. --- ext/iconv/tests/bug52211.phpt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/iconv/tests/bug52211.phpt b/ext/iconv/tests/bug52211.phpt index f213c764de81..fe599f8ac559 100644 --- a/ext/iconv/tests/bug52211.phpt +++ b/ext/iconv/tests/bug52211.phpt @@ -5,8 +5,22 @@ iconv --FILE-- From 4daefbf9f827b4ebf5861d5b1dd557c408764de9 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 8 Jul 2026 16:39:46 +0100 Subject: [PATCH 04/12] bz2: use C23 enum and pack filter data struct (#22641) --- ext/bz2/bz2_filter.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/bz2/bz2_filter.c b/ext/bz2/bz2_filter.c index 09c49fa76687..ec042a51a3b2 100644 --- a/ext/bz2/bz2_filter.c +++ b/ext/bz2/bz2_filter.c @@ -21,7 +21,7 @@ /* {{{ data structure */ -enum strm_status { +C23_ENUM(strm_status, uint8_t) { PHP_BZ2_UNINITIALIZED, PHP_BZ2_RUNNING, PHP_BZ2_FINISHED @@ -34,12 +34,11 @@ typedef struct _php_bz2_filter_data { size_t inbuf_len; size_t outbuf_len; - enum strm_status status; /* Decompress option */ - unsigned int small_footprint : 1; /* Decompress option */ - unsigned int expect_concatenated : 1; /* Decompress option */ - unsigned int is_flushed : 1; /* only for compression */ - - int persistent; + bool persistent; + bool expect_concatenated : 1; /* Decompress option */ + bool small_footprint : 1; /* Decompress option */ + bool is_flushed : 1; /* only for compression */ + strm_status status; /* Decompress option */ /* Configuration for reset - immutable */ int blockSize100k; /* compress only */ From e790541cc5e1a09f9c22dbcddb7a8f497b02274f Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 8 Jul 2026 23:45:11 +0800 Subject: [PATCH 05/12] [skip ci] Add various missing UPGRADING entries for 8.6 BC breaks Dom\DtdNamedNodeMap array access negative index behavior: PR #22630 Dom\DtdNamedNodeMap array access INT_MAX index validation: PR #22630 sleep() maximum seconds validation: PR #22619 usleep() maximum microseconds validation: PR #22619 --- UPGRADING | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/UPGRADING b/UPGRADING index 6a1f1b12e4a8..a6cd3bdef1cb 100644 --- a/UPGRADING +++ b/UPGRADING @@ -32,6 +32,11 @@ PHP 8.6 UPGRADE NOTES from global scope" instead of the prior readonly modification error. ReflectionProperty::isWritable() also reports these properties accurately. + . Array access on Dom\DtdNamedNodeMap objects now returns null for negative + integer indexes instead of returning the first node. + . Array access on Dom\DtdNamedNodeMap objects now raises a ValueError when + the integer index is greater than INT_MAX instead of overflowing to a + smaller index. - GD: . imagesetstyle(), imagefilter() and imagecrop() filter their array arguments @@ -188,6 +193,11 @@ PHP 8.6 UPGRADE NOTES value is passed. . number_format() now raises a ValueError when $decimals is outside the integer range instead of silently clamping very large positive values. + . sleep() now raises a ValueError when $seconds is greater than the platform + limit (UINT_MAX seconds, or UINT_MAX / 1000 seconds on Windows) instead of + allowing the value to overflow. + . usleep() now raises a ValueError when $microseconds is greater than + UINT_MAX instead of allowing the value to overflow. . proc_open() now raises a ValueError when the $cwd argument contains NUL bytes. From 9b1cdc5a98bb90ba3dc3e1ce4e380ffc3ef9a3cd Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 22:07:39 +0100 Subject: [PATCH 06/12] ext/standard: Add test for headers_sent() by ref args --- .../headers_sent_by_ref_args.phpt | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 ext/standard/tests/general_functions/headers_sent_by_ref_args.phpt diff --git a/ext/standard/tests/general_functions/headers_sent_by_ref_args.phpt b/ext/standard/tests/general_functions/headers_sent_by_ref_args.phpt new file mode 100644 index 000000000000..bd222ca91437 --- /dev/null +++ b/ext/standard/tests/general_functions/headers_sent_by_ref_args.phpt @@ -0,0 +1,119 @@ +--TEST-- +headers_sent() by-ref argument with named arguments +--FILE-- + +--EXPECTF-- +headers_sent(line: $line) prior to sending headers +headers_sent(): +bool(false) +headers_list(): +array(0) { +} +$file: +NULL +$line: +int(0) +headers_sent(filename: $file) prior to sending headers +headers_sent(): +bool(false) +headers_list(): +array(0) { +} +$file: +string(0) "" +$line: +NULL +header(): +NULL +headers_sent(line: $line) after sending headers +headers_sent(): +bool(true) +headers_list(): +array(0) { +} +$file: +NULL +$line: +int(39) +headers_sent(filename: $file) after sending headers +headers_sent(): +bool(true) +headers_list(): +array(0) { +} +$file: +string(%d) "%s" +$line: +NULL +Done From 66966e625e0928633572236b2afd811bfeb66fe6 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Sep 2024 22:14:19 +0100 Subject: [PATCH 07/12] ext/standard/head.c: no need to rely on ZEND_NUM_ARGS() --- Zend/tests/weakrefs/gh17442_1.phpt | 2 +- ext/standard/head.c | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Zend/tests/weakrefs/gh17442_1.phpt b/Zend/tests/weakrefs/gh17442_1.phpt index fc7f60174ed9..d535c307e3be 100644 --- a/Zend/tests/weakrefs/gh17442_1.phpt +++ b/Zend/tests/weakrefs/gh17442_1.phpt @@ -17,6 +17,6 @@ headers_sent($obj,$generator); Fatal error: Uncaught Exception: Test in %s:%d Stack trace: #0 [internal function]: class@anonymous->__destruct() -#1 %s(%d): headers_sent(NULL, 0) +#1 %s(%d): headers_sent(NULL, NULL) #2 {main} thrown in %s on line %d diff --git a/ext/standard/head.c b/ext/standard/head.c index 773b6919b780..34a22327b5e7 100644 --- a/ext/standard/head.c +++ b/ext/standard/head.c @@ -314,14 +314,15 @@ PHP_FUNCTION(setrawcookie) /* {{{ Returns true if headers have already been sent, false otherwise */ PHP_FUNCTION(headers_sent) { - zval *arg1 = NULL, *arg2 = NULL; - const char *file=""; - int line=0; + zval *by_ref_filename = NULL; + zval *by_ref_line = NULL; + const char *file = ""; + int line = 0; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(arg1) - Z_PARAM_ZVAL(arg2) + Z_PARAM_ZVAL(by_ref_filename) + Z_PARAM_ZVAL(by_ref_line) ZEND_PARSE_PARAMETERS_END(); if (SG(headers_sent)) { @@ -329,17 +330,15 @@ PHP_FUNCTION(headers_sent) file = php_output_get_start_filename(); } - switch(ZEND_NUM_ARGS()) { - case 2: - ZEND_TRY_ASSIGN_REF_LONG(arg2, line); - ZEND_FALLTHROUGH; - case 1: + if (by_ref_filename) { if (file) { - ZEND_TRY_ASSIGN_REF_STRING(arg1, file); + ZEND_TRY_ASSIGN_REF_STRING(by_ref_filename, file); } else { - ZEND_TRY_ASSIGN_REF_EMPTY_STRING(arg1); + ZEND_TRY_ASSIGN_REF_EMPTY_STRING(by_ref_filename); } - break; + } + if (by_ref_line) { + ZEND_TRY_ASSIGN_REF_LONG(by_ref_line, line); } RETURN_BOOL(SG(headers_sent)); From 6f1beedb446a32ea3f41028a729ddf737c19c91c Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 8 Jul 2026 13:38:48 +0200 Subject: [PATCH 08/12] [skip ci] Fix CXXFLAGS for type verification build The attempted fix in ba32d70 was incorrect as the flags are later overwritten with -D_GLIBCXX_ASSERTIONS. Closes GH-22639 --- .github/workflows/test-suite.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index e8f698e4e091..e269ed336408 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -166,10 +166,10 @@ jobs: with: configurationParameters: >- ${{ matrix.asan && 'CFLAGS="-fsanitize=undefined,address -DZEND_TRACK_ARENA_ALLOC" LDFLAGS="-fsanitize=undefined,address"' || '' }} - ${{ matrix.variation && 'CFLAGS="-DZEND_RC_DEBUG=1 -DPROFITABILITY_CHECKS=0 -DZEND_VERIFY_FUNC_INFO=1 -DZEND_VERIFY_TYPE_INFERENCE" CXXFLAGS="-DZEND_VERIFY_TYPE_INFERENCE"' || '' }} + ${{ matrix.variation && 'CFLAGS="-DZEND_RC_DEBUG=1 -DPROFITABILITY_CHECKS=0 -DZEND_VERIFY_FUNC_INFO=1 -DZEND_VERIFY_TYPE_INFERENCE"' || '' }} ${{ (matrix.variation && fromJson(inputs.branch).jobs.LINUX_X64.config.variation_enable_zend_max_execution_timers) && '--enable-zend-max-execution-timers' || '' }} --${{ matrix.debug && 'enable' || 'disable' }}-debug - ${{ matrix.debug && 'CXXFLAGS="-D_GLIBCXX_ASSERTIONS"' || '' }} + ${{ (matrix.variation || matrix.debug) && format('CXXFLAGS="{0} {1}"', matrix.variation && '-DZEND_VERIFY_TYPE_INFERENCE' || '', matrix.debug && '-D_GLIBCXX_ASSERTIONS' || '') || '' }} --${{ matrix.zts && 'enable' || 'disable' }}-zts asan: ${{ matrix.asan && 'true' || 'false' }} skipSlow: ${{ (matrix.asan && !inputs.all_variations) && 'true' || 'false' }} From ebbfb3c02bcd42bb21ceddb71a26010c1f486d0b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 16 Nov 2024 22:55:07 -0500 Subject: [PATCH 09/12] ext/iconv/tests/eucjp2iso2022jp.phpt: skip for "unknown" iconv This test does a byte comparison of the expected ISO-2022-JP encoding with the result of iconv(). The ISO-2022-JP encoding, however, is stateful; there are many ways to arrive at a correct answer. Musl is known to have an inefficient encoding that causes it to fail this test, so we add a SKIPIF for the "unknown" iconv implementation that musl has. Nothing is wrong here per se, but to support the musl output (and that of other iconvs), an expert would need to verify it. --- ext/iconv/tests/eucjp2iso2022jp.phpt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/iconv/tests/eucjp2iso2022jp.phpt b/ext/iconv/tests/eucjp2iso2022jp.phpt index 6c1a8ec1214b..c3260c370f08 100644 --- a/ext/iconv/tests/eucjp2iso2022jp.phpt +++ b/ext/iconv/tests/eucjp2iso2022jp.phpt @@ -2,6 +2,16 @@ EUC-JP to ISO-2022-JP --EXTENSIONS-- iconv +--SKIPIF-- + --INI-- error_reporting=2039 --FILE-- From 863263a775ec18d25d3c5e035192e780fd828e1c Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 16 Nov 2024 23:13:42 -0500 Subject: [PATCH 10/12] ext/iconv/tests/iconv_mime_encode.phpt: skip for "unknown" iconv This test checks the output of iconv_mime_encode() with a target charset of ISO-2022-JP. That charset is stateful, though, and there are many ways to arrive at a correct answer. Musl has an inefficient encoding of it that causes this to fail because the actual output differs from (and is much longer than) the expected output. We add a SKIPIF for the "unknown" iconv implementation that musl has. --- ext/iconv/tests/iconv_mime_encode.phpt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/iconv/tests/iconv_mime_encode.phpt b/ext/iconv/tests/iconv_mime_encode.phpt index e055f53b016a..c6f41c56ad90 100644 --- a/ext/iconv/tests/iconv_mime_encode.phpt +++ b/ext/iconv/tests/iconv_mime_encode.phpt @@ -2,6 +2,16 @@ iconv_mime_encode() --EXTENSIONS-- iconv +--SKIPIF-- + --INI-- iconv.internal_charset=iso-8859-1 --FILE-- From 8d1d4e3cf8e1502297695a66b88379fa727a8a72 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 9 Jul 2026 01:59:34 +0800 Subject: [PATCH 11/12] Zend: Add new zend APIs for `bin2hex` to remove `ext/hash` usage in various extensions (#21724) There are various extensions in the php code base that includes ext/hash/php_hash for the php_hash_bin2hex method. While these method is actually simple, we can make them as APIs in the zend engine to remove the dependence on ext/hash/ This commit added the needed APIs (zend_bin2hex and zend_bin2hex_str). A follow-up PR will be made in the future to replace the usage of ext/hash with the added APIs. --- UPGRADING.INTERNALS | 2 ++ Zend/zend_string.c | 26 ++++++++++++++++++++++++++ Zend/zend_string.h | 2 ++ 3 files changed, 30 insertions(+) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index adfc91880f29..1c602548b1ed 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -68,6 +68,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES . Added Z_PARAM_ENUM(). . Added zend_enum_fetch_case_id(). . Added zend_enum_get_case_by_id(). + . Added zend_bin2hex() and zend_bin2hex_str() as helper functions to remove + dependencies on /ext/hash in various extensions. . ZEND_INI_GET_ADDR() is now a void* pointer instead of a char* pointer. This more correctly represents the generic nature of the returned pointer and allows to remove explicit casts, but possibly breaks pointer arithmetic diff --git a/Zend/zend_string.c b/Zend/zend_string.c index 52fca0cd4346..b1b4a0e17a69 100644 --- a/Zend/zend_string.c +++ b/Zend/zend_string.c @@ -52,6 +52,17 @@ ZEND_API zend_string *zend_empty_string = NULL; ZEND_API zend_string *zend_one_char_string[256]; ZEND_API zend_string **zend_known_strings = NULL; +/* this is read-only, so it's ok */ +ZEND_SET_ALIGNED(16, static const char zend_hexconvtab[]) = "0123456789abcdef"; + +static zend_always_inline void zend_bin2hex_impl(char *out, const unsigned char *in, size_t in_len, const char *hexconvtab) +{ + for (size_t i = 0; i < in_len; i++) { + out[i * 2] = hexconvtab[in[i] >> 4]; + out[i * 2 + 1] = hexconvtab[in[i] & 0x0f]; + } +} + ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str) { return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str)); @@ -62,6 +73,21 @@ ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len) return zend_inline_hash_func(str, len); } +ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len) +{ + zend_bin2hex_impl(out, in, in_len, zend_hexconvtab); +} + +ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len) +{ + zend_string *result = zend_string_safe_alloc(in_len, 2 * sizeof(char), 0, 0); + + zend_bin2hex(ZSTR_VAL(result), in, in_len); + ZSTR_VAL(result)[in_len * 2] = '\0'; + + return result; +} + static void _str_dtor(zval *zv) { zend_string *str = Z_STR_P(zv); diff --git a/Zend/zend_string.h b/Zend/zend_string.h index e0d1c026a2f2..fdfff2bbc894 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -43,6 +43,8 @@ ZEND_API extern zend_string_init_existing_interned_func_t zend_string_init_exist ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str); ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len); ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str); +ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len); +ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len); ZEND_API zend_string *zend_string_concat2( const char *str1, size_t str1_len, From d0ce868a5a531f701730fd4a184a2ff9cc22d0d7 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 8 Jul 2026 19:06:33 +0100 Subject: [PATCH 12/12] iconv: fix test with duplicate SKIPIF from merge up I didn't get a merge conflict here so assumed it was fine. --- ext/iconv/tests/iconv_mime_encode.phpt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/iconv/tests/iconv_mime_encode.phpt b/ext/iconv/tests/iconv_mime_encode.phpt index 7f4b9bfb5608..75c9b56e4291 100644 --- a/ext/iconv/tests/iconv_mime_encode.phpt +++ b/ext/iconv/tests/iconv_mime_encode.phpt @@ -2,6 +2,8 @@ iconv_mime_encode() --EXTENSIONS-- iconv +--INI-- +iconv.internal_charset=iso-8859-1 --SKIPIF-- ---INI-- -iconv.internal_charset=iso-8859-1 ---SKIPIF-- -