From bf5787f959d3a370895c91952b774e7108bc6be4 Mon Sep 17 00:00:00 2001 From: narenin Date: Tue, 27 Aug 2024 10:46:52 +0530 Subject: [PATCH 1/6] Fixed : Inconsistent behaviour when zero bytes are passed in size_format() --- src/wp-includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 6ddd11f0715a1..1bfcc3e66faeb 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -482,7 +482,7 @@ function size_format( $bytes, $decimals = 0 ) { _x( 'B', 'unit symbol' ) => 1, ); - if ( 0 === $bytes ) { + if ( 0 === $bytes || '0' === $bytes ) { /* translators: Unit symbol for byte. */ return number_format_i18n( 0, $decimals ) . ' ' . _x( 'B', 'unit symbol' ); } From 09394a70e31625a869e55f20ecff9dc00024e31e Mon Sep 17 00:00:00 2001 From: narenin Date: Mon, 2 Sep 2024 10:42:33 +0530 Subject: [PATCH 2/6] Implemented other cases of 0 --- src/wp-includes/functions.php | 4 +++- tests/phpunit/tests/functions/sizeFormat.php | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 1bfcc3e66faeb..da1c86cb9f9b0 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -482,7 +482,9 @@ function size_format( $bytes, $decimals = 0 ) { _x( 'B', 'unit symbol' ) => 1, ); - if ( 0 === $bytes || '0' === $bytes ) { + $bytes = is_numeric( $bytes ) ? (float) $bytes : $bytes; + + if ( 0.0 === $bytes ) { /* translators: Unit symbol for byte. */ return number_format_i18n( 0, $decimals ) . ' ' . _x( 'B', 'unit symbol' ); } diff --git a/tests/phpunit/tests/functions/sizeFormat.php b/tests/phpunit/tests/functions/sizeFormat.php index 77134188634fe..65020d0bab54f 100644 --- a/tests/phpunit/tests/functions/sizeFormat.php +++ b/tests/phpunit/tests/functions/sizeFormat.php @@ -28,6 +28,9 @@ public function data_size_format() { array( -1, 0, false ), // Bytes. array( 0, 0, '0 B' ), + array( '0', 0, '0 B' ), + array( '0.0', 0, '0 B' ), + array( 0.0e3, 0, '0 B' ), array( 1, 0, '1 B' ), array( 1023, 0, '1,023 B' ), // Kilobytes. From 69a742cab7d4af1542ae852bbac4db594fa67080 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 3 Sep 2026 12:21:38 -0700 Subject: [PATCH 3/6] Cast $bytes to int --- src/wp-includes/functions.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index b19a624747b0c..65f4a64bff3c0 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -486,9 +486,8 @@ function size_format( $bytes, $decimals = 0 ) { _x( 'B', 'unit symbol' ) => 1, ); - $bytes = is_numeric( $bytes ) ? (float) $bytes : $bytes; - - if ( 0.0 === $bytes ) { + $bytes = (int) $bytes; + if ( 0 === $bytes ) { /* translators: Unit symbol for byte. */ return number_format_i18n( 0, $decimals ) . ' ' . _x( 'B', 'unit symbol' ); } From caa40b65b9c31efd63eff79f29b97ed50f5b3c07 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 3 Sep 2026 17:22:56 -0700 Subject: [PATCH 4/6] Recognize any numeric zero in size_format() instead of casting to int Casting `$bytes` to `int` made every non-numeric value collapse to zero, so `size_format( array() )`, `size_format( 'baba' )` and `size_format( '' )` returned `'0 B'` rather than `false`. It also failed outright for the ZB and YB magnitudes, whose float values exceed `PHP_INT_MAX` and therefore raise "The float ... is not representable as an int, cast occurred". Guard the zero shortcut with `is_numeric()` and compare as a float instead. This treats `0`, `0.0`, `'0'` and `'0.0'` alike while leaving non-numeric input to fall through to the existing magnitude loop, which returns `false` as before. Extend the data provider to cover the float and numeric-string spellings of zero, a non-default `$decimals` value for zero, and a negative float. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/functions.php | 4 ++-- tests/phpunit/tests/functions/sizeFormat.php | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 65f4a64bff3c0..b033c5b7d19ba 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -486,8 +486,8 @@ function size_format( $bytes, $decimals = 0 ) { _x( 'B', 'unit symbol' ) => 1, ); - $bytes = (int) $bytes; - if ( 0 === $bytes ) { + // Check for a numeric zero of any type, including a float or a numeric string. + if ( is_numeric( $bytes ) && 0.0 === (float) $bytes ) { /* translators: Unit symbol for byte. */ return number_format_i18n( 0, $decimals ) . ' ' . _x( 'B', 'unit symbol' ); } diff --git a/tests/phpunit/tests/functions/sizeFormat.php b/tests/phpunit/tests/functions/sizeFormat.php index 65020d0bab54f..876e3253ba5c2 100644 --- a/tests/phpunit/tests/functions/sizeFormat.php +++ b/tests/phpunit/tests/functions/sizeFormat.php @@ -26,11 +26,16 @@ public function data_size_format() { array( '', 0, false ), array( '-1', 0, false ), array( -1, 0, false ), - // Bytes. + array( -1.0, 0, false ), + // Zero bytes, in every numeric representation. array( 0, 0, '0 B' ), + array( 0.0, 0, '0 B' ), + array( 0.0, 2, '0.00 B' ), array( '0', 0, '0 B' ), array( '0.0', 0, '0 B' ), array( 0.0e3, 0, '0 B' ), + array( -0.0, 0, '0 B' ), + // Bytes. array( 1, 0, '1 B' ), array( 1023, 0, '1,023 B' ), // Kilobytes. From b4c0c1b60a87ea750228045836f9452cd2194a23 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 3 Sep 2026 17:39:40 -0700 Subject: [PATCH 5/6] Document that size_format() accepts a float or a numeric string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core has always passed floats to `size_format()` — `disk_free_space()` from the site health tests, `Imagick::getResourceLimit()` from the debug data, and expressions such as `2.5 * MB_IN_BYTES` — but the docblock only promised `int|string`. Widen the documented type to `int|float|string`, and add a `@phpstan-param` narrowing the string half to `numeric-string`, which is what the function actually accepts now that a non-numeric string falls through to the magnitude loop and returns `false`. This drops one entry from the argument.type baseline, without the underlying report being fixed. `disk_free_space()` returns `float|false`, which shares nothing with `int|string` and so was reported as a wholly incompatible argument at level 5. Against `float|int|numeric-string` only the `false` half is wrong, and a partially wrong union type is a level 7 check, so the error no longer surfaces in the baselines, which are generated from `phpstan.neon.dist` at level 5. The call site is still reported at higher levels and is left to be addressed when the level is raised. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/functions.php | 6 ++++-- tests/phpstan/baselines/argument.type.neon | 5 ----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 52a7216b6ebf3..978aa5a382418 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -460,9 +460,11 @@ function number_format_i18n( $number, $decimals = 0 ) { * @since 2.3.0 * @since 6.0.0 Support for PB, EB, ZB, and YB was added. * - * @param int|string $bytes Number of bytes. Note max integer size for integers. - * @param int $decimals Optional. Precision of number of decimal places. Default 0. + * @param int|float|string $bytes Number of bytes. Note max integer size for integers. + * @param int $decimals Optional. Precision of number of decimal places. Default 0. * @return string|false Number string on success, false on failure. + * + * @phpstan-param int|float|numeric-string $bytes */ function size_format( $bytes, $decimals = 0 ) { $quant = array( diff --git a/tests/phpstan/baselines/argument.type.neon b/tests/phpstan/baselines/argument.type.neon index fb48bd1be342a..66bfb6d6b9585 100644 --- a/tests/phpstan/baselines/argument.type.neon +++ b/tests/phpstan/baselines/argument.type.neon @@ -118,11 +118,6 @@ parameters: identifier: argument.type count: 1 path: ../../../src/wp-admin/includes/class-wp-site-health-auto-updates.php - - - message: '#^Parameter \#1 \$bytes of function size_format expects int\|string, float\|false given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/wp-admin/includes/class-wp-site-health.php - message: '#^Parameter \#2 \$allowed_html of function wp_kses expects array\\|string, array\\|true\> given\.$#' identifier: argument.type From a385148fc746bdf13edb3ffe0853893a9902f6c2 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 3 Sep 2026 17:55:02 -0700 Subject: [PATCH 6/6] Normalize $bytes to a float once in size_format() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `$bytes` was cast to a float twice — once to compare against zero, then again on every iteration of the magnitude loop — with the division juggling it a third time. Cast it once, up front, and compare and divide that float from then on. The cast can only move up if whatever reaches it is known to be numeric, so return `false` before it when it is not. That guard is not just defensive tidying. Non-numeric input previously relied on the loop's own cast to carry it out the bottom of the function, which happened to work only for values that cast to `0.0`: an empty array, an empty string, `'baba'`. A non-empty array or an object casts to `1.0` instead, matches the byte magnitude, and then fatals on `$bytes / $mag` with "Unsupported operand types", so `size_format( array( 'a' ) )` and `size_format( new stdClass() )` were fatal errors. Both now return `false`, as documented. Bailing out early also matches `treatPhpDocTypesAsCertain: false` in the PHPStan configuration: `numeric-string` is not enforced by PHP, so a caller passing something else has to be handled rather than assumed away. One case changes deliberately. `size_format( true )` returned `'1 B'`, since `(float) true` is `1.0`, and now returns `false`. A boolean is not a byte count, and `false` already returned `false` by the accident described above. Extend the data provider with the inputs that fataled, the boolean and null cases, and a hex string, which `is_numeric()` rejects. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/functions.php | 11 ++++++++--- tests/phpunit/tests/functions/sizeFormat.php | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 978aa5a382418..dd42e3ad916f6 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -467,6 +467,12 @@ function number_format_i18n( $number, $decimals = 0 ) { * @phpstan-param int|float|numeric-string $bytes */ function size_format( $bytes, $decimals = 0 ) { + if ( ! is_numeric( $bytes ) ) { + return false; + } + + $bytes = (float) $bytes; + $quant = array( /* translators: Unit symbol for yottabyte. */ _x( 'YB', 'unit symbol' ) => YB_IN_BYTES, @@ -488,14 +494,13 @@ function size_format( $bytes, $decimals = 0 ) { _x( 'B', 'unit symbol' ) => 1, ); - // Check for a numeric zero of any type, including a float or a numeric string. - if ( is_numeric( $bytes ) && 0.0 === (float) $bytes ) { + if ( 0.0 === $bytes ) { /* translators: Unit symbol for byte. */ return number_format_i18n( 0, $decimals ) . ' ' . _x( 'B', 'unit symbol' ); } foreach ( $quant as $unit => $mag ) { - if ( (float) $bytes >= $mag ) { + if ( $bytes >= $mag ) { return number_format_i18n( $bytes / $mag, $decimals ) . ' ' . $unit; } } diff --git a/tests/phpunit/tests/functions/sizeFormat.php b/tests/phpunit/tests/functions/sizeFormat.php index 876e3253ba5c2..b4a038408b47b 100644 --- a/tests/phpunit/tests/functions/sizeFormat.php +++ b/tests/phpunit/tests/functions/sizeFormat.php @@ -22,8 +22,14 @@ public function data_size_format() { return array( // Invalid values. array( array(), 0, false ), + array( array( 'baba' ), 0, false ), + array( new stdClass(), 0, false ), array( 'baba', 0, false ), array( '', 0, false ), + array( '0x1A', 0, false ), + array( null, 0, false ), + array( true, 0, false ), + array( false, 0, false ), array( '-1', 0, false ), array( -1, 0, false ), array( -1.0, 0, false ),