From 5cd96d3e964f0c5e851df0cc73a8ecfa77c066d1 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 21 Jul 2026 17:36:31 +0800 Subject: [PATCH 01/12] ext/standard: Fix `setlocale()` NUL byte truncation --- ext/standard/string.c | 16 ++++++---- .../tests/strings/setlocale_null_byte.phpt | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 ext/standard/tests/strings/setlocale_null_byte.phpt diff --git a/ext/standard/string.c b/ext/standard/string.c index 823c32a8cf1e..e4a86f166895 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4837,12 +4837,16 @@ PHP_FUNCTION(strip_tags) } /* }}} */ -static zend_string *try_setlocale_str(zend_long cat, zend_string *loc) { +static zend_string *try_setlocale_str(zend_long cat, zend_string *loc, uint32_t arg_num) { const char *retval; if (zend_string_equals_literal(loc, "0")) { loc = NULL; } else { + if (zend_str_has_nul_byte(loc)) { + zend_argument_value_error(arg_num, "must not contain any null bytes"); + return NULL; + } if (ZSTR_LEN(loc) >= 255) { php_error_docref(NULL, E_WARNING, "Specified locale name is too long"); return NULL; @@ -4903,13 +4907,13 @@ static zend_string *try_setlocale_str(zend_long cat, zend_string *loc) { return zend_string_init(retval, strlen(retval), 0); } -static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv) { +static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv, uint32_t arg_num) { zend_string *tmp_loc_str; zend_string *loc_str = zval_try_get_tmp_string(loc_zv, &tmp_loc_str); if (UNEXPECTED(loc_str == NULL)) { return NULL; } - zend_string *result = try_setlocale_str(cat, loc_str); + zend_string *result = try_setlocale_str(cat, loc_str, arg_num); zend_tmp_string_release(tmp_loc_str); return result; } @@ -4941,7 +4945,7 @@ PHP_FUNCTION(setlocale) if (Z_TYPE(args[i]) == IS_ARRAY) { zval *elem; ZEND_HASH_FOREACH_VAL(Z_ARRVAL(args[i]), elem) { - result = try_setlocale_zval(cat, elem); + result = try_setlocale_zval(cat, elem, i + 2); if (EG(exception)) { goto out; } @@ -4952,9 +4956,9 @@ PHP_FUNCTION(setlocale) } ZEND_HASH_FOREACH_END(); continue; } else if (Z_ISNULL(args[i])) { - result = try_setlocale_str(cat, ZSTR_EMPTY_ALLOC()); + result = try_setlocale_str(cat, ZSTR_EMPTY_ALLOC(), i + 2); } else { - result = try_setlocale_str(cat, strings[i]); + result = try_setlocale_str(cat, strings[i], i + 2); } if (EG(exception)) { goto out; diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt new file mode 100644 index 000000000000..d55f711b02ac --- /dev/null +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -0,0 +1,32 @@ +--TEST-- +setlocale() rejects locale names with null bytes +--FILE-- +getMessage(), "\n"; +} + +try { + var_dump(setlocale(LC_ALL, ["invalid\0locale", "C"])); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(setlocale(LC_ALL, [], new NullByteStringable())); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +setlocale(): Argument #2 ($locales) must not contain any null bytes +setlocale(): Argument #2 ($locales) must not contain any null bytes +setlocale(): Argument #3 must not contain any null bytes From 4df3507fc4ce9d8fdeb7dfde5649cd70d5f6c20e Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 21 Jul 2026 17:45:55 +0800 Subject: [PATCH 02/12] NEWS and UPGRADING --- NEWS | 4 ++++ UPGRADING | 2 ++ 2 files changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 79a15820a902..cdcf165e8528 100644 --- a/NEWS +++ b/NEWS @@ -59,6 +59,10 @@ PHP NEWS TCP_USER_TIMEOUT, and SO_LINGER options. (Weilin Du) . Fixed various memory related issues in ext/sockets. (David Carlier) +- Standard: + . Fixed setlocale() to reject locale names containing NUL bytes instead of + silently truncating them. (Weilin Du) + - Streams: . Added a new IO copy API used by php_stream_copy_to_stream_ex() that leverages platform primitives (sendfile, splice, copy_file_range, diff --git a/UPGRADING b/UPGRADING index 97670e09246d..99f59abd8453 100644 --- a/UPGRADING +++ b/UPGRADING @@ -201,6 +201,8 @@ PHP 8.6 UPGRADE NOTES bytes. . parse_str() now raises a ValueError when the $string argument contains NUL bytes. + . setlocale() now raises a ValueError when a locale name contains NUL bytes, + instead of silently truncating it. . linkinfo() now raises a ValueError when the $path argument is empty. . pathinfo() now raises a ValueError when an invalid $flag argument value is passed. From e62074f0bb62c169f7cb6d2cafe37e800718e30b Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 21 Jul 2026 22:30:29 +0800 Subject: [PATCH 03/12] Use zend_str_has_nul_byte to directly throw error --- ext/standard/string.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ext/standard/string.c b/ext/standard/string.c index e4a86f166895..56bf6df87160 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4837,16 +4837,12 @@ PHP_FUNCTION(strip_tags) } /* }}} */ -static zend_string *try_setlocale_str(zend_long cat, zend_string *loc, uint32_t arg_num) { +static zend_string *try_setlocale_str(zend_long cat, zend_string *loc) { const char *retval; if (zend_string_equals_literal(loc, "0")) { loc = NULL; } else { - if (zend_str_has_nul_byte(loc)) { - zend_argument_value_error(arg_num, "must not contain any null bytes"); - return NULL; - } if (ZSTR_LEN(loc) >= 255) { php_error_docref(NULL, E_WARNING, "Specified locale name is too long"); return NULL; @@ -4907,13 +4903,18 @@ static zend_string *try_setlocale_str(zend_long cat, zend_string *loc, uint32_t return zend_string_init(retval, strlen(retval), 0); } -static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv, uint32_t arg_num) { +static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv) { zend_string *tmp_loc_str; zend_string *loc_str = zval_try_get_tmp_string(loc_zv, &tmp_loc_str); if (UNEXPECTED(loc_str == NULL)) { return NULL; } - zend_string *result = try_setlocale_str(cat, loc_str, arg_num); + if (zend_str_has_nul_byte(loc_str)) { + zend_argument_value_error(2, "must not contain any null bytes"); + zend_tmp_string_release(tmp_loc_str); + return NULL; + } + zend_string *result = try_setlocale_str(cat, loc_str); zend_tmp_string_release(tmp_loc_str); return result; } @@ -4934,8 +4935,11 @@ PHP_FUNCTION(setlocale) zend_string **strings = do_alloca(sizeof(zend_string *) * num_args, use_heap); for (uint32_t i = 0; i < num_args; i++) { - if (UNEXPECTED(Z_TYPE(args[i]) != IS_ARRAY && !zend_parse_arg_str(&args[i], &strings[i], true, i + 2))) { - zend_wrong_parameter_type_error(i + 2, Z_EXPECTED_ARRAY_OR_STRING_OR_NULL, &args[i]); + if (UNEXPECTED(Z_TYPE(args[i]) != IS_ARRAY && !zend_parse_arg_path_str(&args[i], &strings[i], true, i + 2))) { + zend_wrong_parameter_type_error( + i + 2, + Z_TYPE(args[i]) == IS_STRING ? Z_EXPECTED_PATH : Z_EXPECTED_ARRAY_OR_STRING_OR_NULL, + &args[i]); goto out; } } @@ -4945,7 +4949,7 @@ PHP_FUNCTION(setlocale) if (Z_TYPE(args[i]) == IS_ARRAY) { zval *elem; ZEND_HASH_FOREACH_VAL(Z_ARRVAL(args[i]), elem) { - result = try_setlocale_zval(cat, elem, i + 2); + result = try_setlocale_zval(cat, elem); if (EG(exception)) { goto out; } @@ -4956,9 +4960,9 @@ PHP_FUNCTION(setlocale) } ZEND_HASH_FOREACH_END(); continue; } else if (Z_ISNULL(args[i])) { - result = try_setlocale_str(cat, ZSTR_EMPTY_ALLOC(), i + 2); + result = try_setlocale_str(cat, ZSTR_EMPTY_ALLOC()); } else { - result = try_setlocale_str(cat, strings[i], i + 2); + result = try_setlocale_str(cat, strings[i]); } if (EG(exception)) { goto out; From 2fcd1631a95395b93b8f02b04e1883d01e964b69 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 22 Jul 2026 03:58:43 +0800 Subject: [PATCH 04/12] Add suggested test case Co-Authored-By: Jorg Adam Sowa <74921107+jorgsowa@users.noreply.github.com> --- ext/standard/tests/strings/setlocale_null_byte.phpt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index d55f711b02ac..1f8ef187277b 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -20,6 +20,12 @@ try { echo $e->getMessage(), "\n"; } +try { + var_dump(setlocale(LC_ALL, ["invalid", "C\0invalid"])); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + try { var_dump(setlocale(LC_ALL, [], new NullByteStringable())); } catch (ValueError $e) { @@ -29,4 +35,5 @@ try { --EXPECT-- setlocale(): Argument #2 ($locales) must not contain any null bytes setlocale(): Argument #2 ($locales) must not contain any null bytes +setlocale(): Argument #2 ($locales) must not contain any null bytes setlocale(): Argument #3 must not contain any null bytes From 48f1f2ec8fb721ba140badd8cd722081780d3038 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 22 Jul 2026 15:36:48 +0800 Subject: [PATCH 05/12] fix CI Co-Authored-By: Jorg Adam Sowa <74921107+jorgsowa@users.noreply.github.com> --- ext/standard/tests/strings/setlocale_null_byte.phpt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index 1f8ef187277b..e84b8f8d203f 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -4,24 +4,24 @@ setlocale() rejects locale names with null bytes getMessage(), "\n"; } try { - var_dump(setlocale(LC_ALL, ["invalid\0locale", "C"])); + var_dump(setlocale(LC_ALL, ["locale\0name", "C"])); } catch (ValueError $e) { echo $e->getMessage(), "\n"; } try { - var_dump(setlocale(LC_ALL, ["invalid", "C\0invalid"])); + var_dump(@setlocale(LC_ALL, [str_repeat("x", 255), "C\0locale"])); } catch (ValueError $e) { echo $e->getMessage(), "\n"; } From 433f05f3a188ab0d3fcd7afe302349a45b795fcf Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 17:22:33 +0800 Subject: [PATCH 06/12] feedback --- ext/standard/string.c | 20 ++++++++--- .../tests/strings/setlocale_null_byte.phpt | 35 ++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/ext/standard/string.c b/ext/standard/string.c index 56bf6df87160..79d44f776d7d 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4903,14 +4903,14 @@ static zend_string *try_setlocale_str(zend_long cat, zend_string *loc) { return zend_string_init(retval, strlen(retval), 0); } -static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv) { +static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv, uint32_t arg_num) { zend_string *tmp_loc_str; zend_string *loc_str = zval_try_get_tmp_string(loc_zv, &tmp_loc_str); if (UNEXPECTED(loc_str == NULL)) { return NULL; } if (zend_str_has_nul_byte(loc_str)) { - zend_argument_value_error(2, "must not contain any null bytes"); + zend_argument_value_error(arg_num, "must not contain any null bytes"); zend_tmp_string_release(tmp_loc_str); return NULL; } @@ -4935,10 +4935,20 @@ PHP_FUNCTION(setlocale) zend_string **strings = do_alloca(sizeof(zend_string *) * num_args, use_heap); for (uint32_t i = 0; i < num_args; i++) { - if (UNEXPECTED(Z_TYPE(args[i]) != IS_ARRAY && !zend_parse_arg_path_str(&args[i], &strings[i], true, i + 2))) { + if (Z_TYPE(args[i]) == IS_ARRAY) { + if (UNEXPECTED(i != 0)) { + zend_wrong_parameter_type_error(i + 2, Z_EXPECTED_STRING_OR_NULL, &args[i]); + goto out; + } + num_args = 1; + break; + } + if (UNEXPECTED(!zend_parse_arg_path_str(&args[i], &strings[i], true, i + 2))) { zend_wrong_parameter_type_error( i + 2, - Z_TYPE(args[i]) == IS_STRING ? Z_EXPECTED_PATH : Z_EXPECTED_ARRAY_OR_STRING_OR_NULL, + Z_TYPE(args[i]) == IS_STRING + ? Z_EXPECTED_PATH + : (i == 0 ? Z_EXPECTED_ARRAY_OR_STRING_OR_NULL : Z_EXPECTED_STRING_OR_NULL), &args[i]); goto out; } @@ -4949,7 +4959,7 @@ PHP_FUNCTION(setlocale) if (Z_TYPE(args[i]) == IS_ARRAY) { zval *elem; ZEND_HASH_FOREACH_VAL(Z_ARRVAL(args[i]), elem) { - result = try_setlocale_zval(cat, elem); + result = try_setlocale_zval(cat, elem, i + 2); if (EG(exception)) { goto out; } diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index e84b8f8d203f..4c375d2616b6 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -11,29 +11,46 @@ class NullByteStringable { try { var_dump(setlocale(LC_ALL, "C\0locale")); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } try { var_dump(setlocale(LC_ALL, ["locale\0name", "C"])); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } try { var_dump(@setlocale(LC_ALL, [str_repeat("x", 255), "C\0locale"])); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } try { - var_dump(setlocale(LC_ALL, [], new NullByteStringable())); + var_dump(setlocale(LC_ALL, ["invalid", "C\0locale"])); } catch (ValueError $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } + +try { + var_dump(setlocale(LC_ALL, "invalid", ["C\0locale"])); +} catch (TypeError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +try { + var_dump(setlocale(LC_ALL, "invalid", new NullByteStringable())); +} catch (ValueError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +var_dump(setlocale(LC_ALL, [], new NullByteStringable())); ?> --EXPECT-- -setlocale(): Argument #2 ($locales) must not contain any null bytes -setlocale(): Argument #2 ($locales) must not contain any null bytes -setlocale(): Argument #2 ($locales) must not contain any null bytes -setlocale(): Argument #3 must not contain any null bytes +ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes +ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes +ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes +ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes +TypeError: setlocale(): Argument #3 must be of type ?string, array given +ValueError: setlocale(): Argument #3 must not contain any null bytes +bool(false) From 5ea5b6720acb4286f2bba70ff9f53662b91f5a9a Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 17:26:36 +0800 Subject: [PATCH 07/12] NEWS and UPGRADING --- NEWS | 3 ++- UPGRADING | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index cdcf165e8528..e65a8cb83dbb 100644 --- a/NEWS +++ b/NEWS @@ -61,7 +61,8 @@ PHP NEWS - Standard: . Fixed setlocale() to reject locale names containing NUL bytes instead of - silently truncating them. (Weilin Du) + silently truncating them, and to reject arrays passed after the $locales + argument. (Weilin Du) - Streams: . Added a new IO copy API used by php_stream_copy_to_stream_ex() that diff --git a/UPGRADING b/UPGRADING index 99f59abd8453..edd360bfde0f 100644 --- a/UPGRADING +++ b/UPGRADING @@ -203,6 +203,9 @@ PHP 8.6 UPGRADE NOTES bytes. . setlocale() now raises a ValueError when a locale name contains NUL bytes, instead of silently truncating it. + Arrays are now accepted only for the $locales argument. Passing an array as + a later variadic locale argument now throws a TypeError; if $locales is an + array, only that array is processed. . linkinfo() now raises a ValueError when the $path argument is empty. . pathinfo() now raises a ValueError when an invalid $flag argument value is passed. From 0edf59f2c9256a97548f137f07d2dd1a92392f30 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 17:51:46 +0800 Subject: [PATCH 08/12] feedback --- NEWS | 3 ++- UPGRADING | 5 +++-- ext/standard/string.c | 12 +++++++++--- ext/standard/tests/strings/setlocale_null_byte.phpt | 10 +++++++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index e65a8cb83dbb..0556d7857e20 100644 --- a/NEWS +++ b/NEWS @@ -62,7 +62,8 @@ PHP NEWS - Standard: . Fixed setlocale() to reject locale names containing NUL bytes instead of silently truncating them, and to reject arrays passed after the $locales - argument. (Weilin Du) + argument or additional arguments passed after an array $locales argument. + (Weilin Du) - Streams: . Added a new IO copy API used by php_stream_copy_to_stream_ex() that diff --git a/UPGRADING b/UPGRADING index edd360bfde0f..1b37cdd95104 100644 --- a/UPGRADING +++ b/UPGRADING @@ -204,8 +204,9 @@ PHP 8.6 UPGRADE NOTES . setlocale() now raises a ValueError when a locale name contains NUL bytes, instead of silently truncating it. Arrays are now accepted only for the $locales argument. Passing an array as - a later variadic locale argument now throws a TypeError; if $locales is an - array, only that array is processed. + a later variadic locale argument now throws a TypeError. Passing any + additional locale arguments when $locales is an array now throws an + ArgumentCountError. . linkinfo() now raises a ValueError when the $path argument is empty. . pathinfo() now raises a ValueError when an invalid $flag argument value is passed. diff --git a/ext/standard/string.c b/ext/standard/string.c index 79d44f776d7d..6767ebad68a4 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4903,14 +4903,14 @@ static zend_string *try_setlocale_str(zend_long cat, zend_string *loc) { return zend_string_init(retval, strlen(retval), 0); } -static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv, uint32_t arg_num) { +static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv) { zend_string *tmp_loc_str; zend_string *loc_str = zval_try_get_tmp_string(loc_zv, &tmp_loc_str); if (UNEXPECTED(loc_str == NULL)) { return NULL; } if (zend_str_has_nul_byte(loc_str)) { - zend_argument_value_error(arg_num, "must not contain any null bytes"); + zend_argument_value_error(2, "must not contain any null bytes"); zend_tmp_string_release(tmp_loc_str); return NULL; } @@ -4940,6 +4940,12 @@ PHP_FUNCTION(setlocale) zend_wrong_parameter_type_error(i + 2, Z_EXPECTED_STRING_OR_NULL, &args[i]); goto out; } + if (UNEXPECTED(num_args > 1)) { + zend_argument_count_error( + "setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, %d given", + ZEND_NUM_ARGS()); + goto out; + } num_args = 1; break; } @@ -4959,7 +4965,7 @@ PHP_FUNCTION(setlocale) if (Z_TYPE(args[i]) == IS_ARRAY) { zval *elem; ZEND_HASH_FOREACH_VAL(Z_ARRVAL(args[i]), elem) { - result = try_setlocale_zval(cat, elem, i + 2); + result = try_setlocale_zval(cat, elem); if (EG(exception)) { goto out; } diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index 4c375d2616b6..a2ab25a3ebb5 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -44,13 +44,17 @@ try { echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } -var_dump(setlocale(LC_ALL, [], new NullByteStringable())); +try { + var_dump(setlocale(LC_ALL, [], new NullByteStringable())); +} catch (ArgumentCountError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} ?> --EXPECT-- ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes -TypeError: setlocale(): Argument #3 must be of type ?string, array given +TypeError: setlocale(): Argument #3 must be of type array|string|null, int given ValueError: setlocale(): Argument #3 must not contain any null bytes -bool(false) +ArgumentCountError: setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, 3 given From 09bd7fc89a2ca76f6789379449e1995a4e16c725 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 18:29:16 +0800 Subject: [PATCH 09/12] feedback & fix CI --- ext/standard/string.c | 1 - ext/standard/tests/strings/gh18823_strict.phpt | 2 +- ext/standard/tests/strings/setlocale_null_byte.phpt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/standard/string.c b/ext/standard/string.c index 6767ebad68a4..d8a7479d2a98 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4946,7 +4946,6 @@ PHP_FUNCTION(setlocale) ZEND_NUM_ARGS()); goto out; } - num_args = 1; break; } if (UNEXPECTED(!zend_parse_arg_path_str(&args[i], &strings[i], true, i + 2))) { diff --git a/ext/standard/tests/strings/gh18823_strict.phpt b/ext/standard/tests/strings/gh18823_strict.phpt index 3735eab00670..39ae11ef61bc 100644 --- a/ext/standard/tests/strings/gh18823_strict.phpt +++ b/ext/standard/tests/strings/gh18823_strict.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- setlocale(): Argument #2 ($locales) must be of type array|string|null, int given -setlocale(): Argument #3 must be of type array|string|null, int given +setlocale(): Argument #3 must be of type ?string, int given diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index a2ab25a3ebb5..a72afa672b4f 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -55,6 +55,6 @@ ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes -TypeError: setlocale(): Argument #3 must be of type array|string|null, int given +TypeError: setlocale(): Argument #3 must be of type ?string, array given ValueError: setlocale(): Argument #3 must not contain any null bytes ArgumentCountError: setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, 3 given From 14dba1f8fca8d683f963093ffbed7abedbbf8f0a Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 19:44:24 +0800 Subject: [PATCH 10/12] fix CI --- ext/standard/tests/strings/setlocale_null_byte.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index a72afa672b4f..757d0a9af8e3 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -27,7 +27,7 @@ try { } try { - var_dump(setlocale(LC_ALL, ["invalid", "C\0locale"])); + var_dump(setlocale(LC_ALL, ["en_US.invalid", "C\0locale"])); } catch (ValueError $e) { echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } From 74441c925b93ef0328cf8efd982655d666c1078e Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 20:35:11 +0800 Subject: [PATCH 11/12] use zz_ZZ.nope because we need to support musl --- ext/standard/tests/strings/setlocale_null_byte.phpt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index 757d0a9af8e3..6a6a75597f99 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -27,19 +27,19 @@ try { } try { - var_dump(setlocale(LC_ALL, ["en_US.invalid", "C\0locale"])); + var_dump(setlocale(LC_ALL, ["zz_ZZ.nope", "C\0locale"])); } catch (ValueError $e) { echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } try { - var_dump(setlocale(LC_ALL, "invalid", ["C\0locale"])); + var_dump(setlocale(LC_ALL, "zz_ZZ.nope", ["C\0locale"])); } catch (TypeError $e) { echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } try { - var_dump(setlocale(LC_ALL, "invalid", new NullByteStringable())); + var_dump(setlocale(LC_ALL, "zz_ZZ.nope", new NullByteStringable())); } catch (ValueError $e) { echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } From 4b9c769f98d9f8b114faf0df12744b4469cc91e7 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 22:22:39 +0800 Subject: [PATCH 12/12] Remove flaky setlocale null byte test case --- ext/standard/tests/strings/setlocale_null_byte.phpt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt index 6a6a75597f99..85663b98a71a 100644 --- a/ext/standard/tests/strings/setlocale_null_byte.phpt +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -26,12 +26,6 @@ try { echo $e::class, ": ", $e->getMessage(), \PHP_EOL; } -try { - var_dump(setlocale(LC_ALL, ["zz_ZZ.nope", "C\0locale"])); -} catch (ValueError $e) { - echo $e::class, ": ", $e->getMessage(), \PHP_EOL; -} - try { var_dump(setlocale(LC_ALL, "zz_ZZ.nope", ["C\0locale"])); } catch (TypeError $e) { @@ -54,7 +48,6 @@ try { ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes -ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes TypeError: setlocale(): Argument #3 must be of type ?string, array given ValueError: setlocale(): Argument #3 must not contain any null bytes ArgumentCountError: setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, 3 given