From 46b44cdda26faee137a7c6ba4209951553e9d8c4 Mon Sep 17 00:00:00 2001 From: Huzaifa Al Mesbah Date: Tue, 6 Oct 2026 09:22:50 +0600 Subject: [PATCH 1/2] Charset: Accurately type by-ref parameters in _wp_scan_utf8 and _wp_utf8_codepoint_span --- phpstan.neon.dist | 1 - src/wp-includes/compat-utf8.php | 12 +-- .../baselines/parameterByRef.type.neon | 5 -- .../baselines/parameterByRef.unusedType.neon | 30 ------- tests/phpunit/tests/compat/wpScanUtf8.php | 83 +++++++++++++++++++ 5 files changed, 90 insertions(+), 41 deletions(-) delete mode 100644 tests/phpstan/baselines/parameterByRef.unusedType.neon create mode 100644 tests/phpunit/tests/compat/wpScanUtf8.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index f324ca1827e52..61f406c4bba72 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -60,7 +60,6 @@ includes: - tests/phpstan/baselines/parameter.defaultValue.neon - tests/phpstan/baselines/parameter.notFound.neon - tests/phpstan/baselines/parameterByRef.type.neon - - tests/phpstan/baselines/parameterByRef.unusedType.neon - tests/phpstan/baselines/property.defaultValue.neon - tests/phpstan/baselines/property.nonObject.neon - tests/phpstan/baselines/property.notFound.neon diff --git a/src/wp-includes/compat-utf8.php b/src/wp-includes/compat-utf8.php index 5fa8cde158789..7ebc5d1504bab 100644 --- a/src/wp-includes/compat-utf8.php +++ b/src/wp-includes/compat-utf8.php @@ -42,6 +42,7 @@ * @param int|null $max_bytes Stop scanning after this many bytes have been seen. * @param int|null $max_code_points Stop scanning after this many code points have been seen. * @param bool|null $has_noncharacters Set to indicate if scanned string contained noncharacters. + * @param-out bool $has_noncharacters * @return int How many code points were successfully scanned. */ function _wp_scan_utf8( string $bytes, int &$at, int &$invalid_length, ?int $max_bytes = null, ?int $max_code_points = null, ?bool &$has_noncharacters = null ): int { @@ -151,7 +152,7 @@ function _wp_scan_utf8( string $bytes, int &$at, int &$invalid_length, ?int $max // Covers the range U+FDD0–U+FDEF, U+FFFE, U+FFFF. if ( 0xEF === $b1 ) { - $has_noncharacters |= ( + $has_noncharacters = $has_noncharacters || ( ( 0xB7 === $b2 && $b3 >= 0x90 && $b3 <= 0xAF ) || ( 0xBF === $b2 && ( 0xBE === $b3 || 0xBF === $b3 ) ) ); @@ -176,7 +177,7 @@ function _wp_scan_utf8( string $bytes, int &$at, int &$invalid_length, ?int $max $i += 3; // Covers U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, …, U+10FFFE, U+10FFFF. - $has_noncharacters |= ( + $has_noncharacters = $has_noncharacters || ( ( 0x0F === ( $b2 & 0x0F ) ) && 0xBF === $b3 && ( 0xBE === $b4 || 0xBF === $b4 ) @@ -373,9 +374,10 @@ function _wp_utf8_codepoint_count( string $text, ?int $byte_offset = 0, ?int $ma * @param int $byte_offset Start counting at this byte offset. * @param int $max_code_points Stop counting after this many code points have been seen, * or at the end of the string. - * @param ?int $found_code_points Optional. Will be set to number of found code points in - * span, as this might be smaller than the maximum count if - * the string is not long enough. + * @param int|null $found_code_points Optional. Will be set to number of found code points in + * span, as this might be smaller than the maximum count if + * the string is not long enough. Default 0. + * @param-out int $found_code_points * @return int Number of bytes spanned by the code points. */ function _wp_utf8_codepoint_span( string $text, int $byte_offset, int $max_code_points, ?int &$found_code_points = 0 ): int { diff --git a/tests/phpstan/baselines/parameterByRef.type.neon b/tests/phpstan/baselines/parameterByRef.type.neon index 8b7394add3c7e..1d85af314aa6f 100644 --- a/tests/phpstan/baselines/parameterByRef.type.neon +++ b/tests/phpstan/baselines/parameterByRef.type.neon @@ -38,11 +38,6 @@ parameters: identifier: parameterByRef.type count: 1 path: ../../../src/wp-includes/class-wp-token-map.php - - - message: '#^Parameter &\$has_noncharacters by\-ref type of function _wp_scan_utf8\(\) expects bool\|null, int given\.$#' - identifier: parameterByRef.type - count: 2 - path: ../../../src/wp-includes/compat-utf8.php - message: '#^Parameter &\$result by\-ref type of function _page_traverse_name\(\) expects array\, array given\.$#' identifier: parameterByRef.type diff --git a/tests/phpstan/baselines/parameterByRef.unusedType.neon b/tests/phpstan/baselines/parameterByRef.unusedType.neon deleted file mode 100644 index 73746ef20c078..0000000000000 --- a/tests/phpstan/baselines/parameterByRef.unusedType.neon +++ /dev/null @@ -1,30 +0,0 @@ -# PHPStan baseline for the `parameterByRef.unusedType` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/parameterByRef.unusedType -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=parameterByRef.unusedType -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Function _wp_scan_utf8\(\) never assigns null to &\$has_noncharacters so it can be removed from the by\-ref type\.$#' - identifier: parameterByRef.unusedType - count: 1 - path: ../../../src/wp-includes/compat-utf8.php - - - message: '#^Function _wp_utf8_codepoint_span\(\) never assigns null to &\$found_code_points so it can be removed from the by\-ref type\.$#' - identifier: parameterByRef.unusedType - count: 1 - path: ../../../src/wp-includes/compat-utf8.php diff --git a/tests/phpunit/tests/compat/wpScanUtf8.php b/tests/phpunit/tests/compat/wpScanUtf8.php new file mode 100644 index 0000000000000..82f994977234b --- /dev/null +++ b/tests/phpunit/tests/compat/wpScanUtf8.php @@ -0,0 +1,83 @@ +assertSame( 13, $scanned ); + $this->assertSame( 0, $invalid_length ); + $this->assertIsBool( $has_noncharacters ); + $this->assertFalse( $has_noncharacters ); + } + + /** + * Tests scanning UTF-8 string containing noncharacters. + * + * @ticket 65817 + * + * @dataProvider data_strings_with_noncharacters + * + * @param string $text UTF-8 string containing a noncharacter. + */ + public function test_scan_string_with_noncharacters( string $text ) { + $at = 0; + $invalid_length = 0; + $has_noncharacters = null; + + _wp_scan_utf8( $text, $at, $invalid_length, null, null, $has_noncharacters ); + + $this->assertIsBool( $has_noncharacters ); + $this->assertTrue( $has_noncharacters ); + } + + /** + * Data provider for noncharacters. + * + * @return array + */ + public static function data_strings_with_noncharacters() { + return array( + 'U+FFFF (3-byte noncharacter)' => array( "test \xEF\xBF\xBF end" ), + 'U+FFFE (3-byte noncharacter)' => array( "test \xEF\xBF\xBE end" ), + 'U+FDD0 (3-byte noncharacter)' => array( "test \xEF\xB7\x90 end" ), + 'U+1FFFF (4-byte noncharacter)' => array( "test \xF0\x9F\xBF\xBF end" ), + ); + } + + /** + * Tests scanning UTF-8 string without noncharacters. + * + * @ticket 65817 + */ + public function test_scan_string_without_noncharacters() { + $text = 'WordPress: 日本語 and Piña!'; + $at = 0; + $invalid_length = 0; + $has_noncharacters = null; + + _wp_scan_utf8( $text, $at, $invalid_length, null, null, $has_noncharacters ); + + $this->assertIsBool( $has_noncharacters ); + $this->assertFalse( $has_noncharacters ); + } +} From 2b1bd3fe6c8721e980c3c011dcb92e170cae5f00 Mon Sep 17 00:00:00 2001 From: Huzaifa Al Mesbah Date: Mon, 2 Nov 2026 09:01:30 +0600 Subject: [PATCH 2/2] Tests: Remove redundant boolean type assertions in _wp_scan_utf8() tests. `assertTrue()` and `assertFalse()` already check the boolean type. --- tests/phpunit/tests/compat/wpScanUtf8.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/phpunit/tests/compat/wpScanUtf8.php b/tests/phpunit/tests/compat/wpScanUtf8.php index 82f994977234b..7a9c8bdbc4a9d 100644 --- a/tests/phpunit/tests/compat/wpScanUtf8.php +++ b/tests/phpunit/tests/compat/wpScanUtf8.php @@ -26,7 +26,6 @@ public function test_scan_ascii_string() { $this->assertSame( 13, $scanned ); $this->assertSame( 0, $invalid_length ); - $this->assertIsBool( $has_noncharacters ); $this->assertFalse( $has_noncharacters ); } @@ -46,7 +45,6 @@ public function test_scan_string_with_noncharacters( string $text ) { _wp_scan_utf8( $text, $at, $invalid_length, null, null, $has_noncharacters ); - $this->assertIsBool( $has_noncharacters ); $this->assertTrue( $has_noncharacters ); } @@ -77,7 +75,6 @@ public function test_scan_string_without_noncharacters() { _wp_scan_utf8( $text, $at, $invalid_length, null, null, $has_noncharacters ); - $this->assertIsBool( $has_noncharacters ); $this->assertFalse( $has_noncharacters ); } }