From 7f8eeb8e05382400931f06addd30b7242f869155 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 1 Jul 2026 20:34:16 +0800 Subject: [PATCH 1/5] ext/Intl: Fix IntlDateFormatter offset type validation --- NEWS | 3 ++ ext/intl/dateformat/dateformat_parse.cpp | 18 +++++--- ...fmt_parse_localtime_offset_type_error.phpt | 44 +++++++++++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 ext/intl/tests/datefmt_parse_localtime_offset_type_error.phpt diff --git a/NEWS b/NEWS index 6ba018dab89d..0ba84b077000 100644 --- a/NEWS +++ b/NEWS @@ -97,6 +97,9 @@ PHP NEWS locale_get_display_keyword_value() respectively. (Weilin Du) . Fix incorrect argument positions for invalid start/end arguments in transliterator_transliterate(). (Weilin Du) + . IntlDateFormatter::parse()/datefmt_parse() and + IntlDateFormatter::localtime()/datefmt_localtime() now raise TypeError + when the offset argument is not of type int. (Weilin Du) . Fixed IntlTimeZone::getDisplayName() to synchronize object error state for invalid display types. (Weilin Du) . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the diff --git a/ext/intl/dateformat/dateformat_parse.cpp b/ext/intl/dateformat/dateformat_parse.cpp index d818627439e3..0537b42feec5 100644 --- a/ext/intl/dateformat/dateformat_parse.cpp +++ b/ext/intl/dateformat/dateformat_parse.cpp @@ -147,9 +147,12 @@ U_CFUNC PHP_FUNCTION(datefmt_parse) DATE_FORMAT_METHOD_FETCH_OBJECT; if (z_parse_pos) { - zval *z_parse_pos_tmp = z_parse_pos; - ZVAL_DEREF(z_parse_pos_tmp); - const zend_long long_parse_pos = zval_get_long(z_parse_pos_tmp); + bool failed; + const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); + if (failed) { + zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); + RETURN_THROWS(); + } if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range."); @@ -229,9 +232,12 @@ U_CFUNC PHP_FUNCTION(datefmt_localtime) DATE_FORMAT_METHOD_FETCH_OBJECT; if (z_parse_pos) { - zval *z_parse_pos_tmp = z_parse_pos; - ZVAL_DEREF(z_parse_pos_tmp); - const zend_long long_parse_pos = zval_get_long(z_parse_pos_tmp); + bool failed; + const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); + if (failed) { + zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); + RETURN_THROWS(); + } if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range."); diff --git a/ext/intl/tests/datefmt_parse_localtime_offset_type_error.phpt b/ext/intl/tests/datefmt_parse_localtime_offset_type_error.phpt new file mode 100644 index 000000000000..5c3d03cd4f65 --- /dev/null +++ b/ext/intl/tests/datefmt_parse_localtime_offset_type_error.phpt @@ -0,0 +1,44 @@ +--TEST-- +datefmt_parse() and datefmt_localtime() validate offset type +--EXTENSIONS-- +intl +--FILE-- +setPattern('VV'); + +$offset = 'offset'; +try { + $fmt->parse('America/Los_Angeles', $offset); +} catch (TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +$offset = 'offset'; +try { + datefmt_parse($fmt, 'America/Los_Angeles', $offset); +} catch (TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +$offset = 'offset'; +try { + $fmt->localtime('America/Los_Angeles', $offset); +} catch (TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +$offset = 'offset'; +try { + datefmt_localtime($fmt, 'America/Los_Angeles', $offset); +} catch (TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +IntlDateFormatter::parse(): Argument #2 ($offset) must be of type int, string given +datefmt_parse(): Argument #3 ($offset) must be of type int, string given +IntlDateFormatter::localtime(): Argument #2 ($offset) must be of type int, string given +datefmt_localtime(): Argument #3 ($offset) must be of type int, string given From dbfb662d718a8c69e6527e2aa93b3cb8889daaaa Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 1 Jul 2026 23:57:34 +0800 Subject: [PATCH 2/5] Update UPGRADING --- UPGRADING | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADING b/UPGRADING index fa9a816f7aed..e0d5d476e028 100644 --- a/UPGRADING +++ b/UPGRADING @@ -38,6 +38,10 @@ PHP 8.6 UPGRADE NOTES types / values and raise a TypeError / ValueError accordingly. - Intl: + . IntlDateFormatter::parse()/datefmt_parse() and + IntlDateFormatter::localtime()/datefmt_localtime() now raise a TypeError + when the offset argument is not of type int instead of silently converting + the value. . Passing a non-stringable object as a time zone to Intl APIs that accept time zone objects or strings now raises a TypeError instead of an Error. . IntlBreakIterator::getLocale() now raises a ValueError when the type is From 068c5ea9ae8e3f837d686f446694172cbd7ce5c0 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 2 Jul 2026 00:10:42 +0800 Subject: [PATCH 3/5] Update UPGRADING --- UPGRADING | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UPGRADING b/UPGRADING index e0d5d476e028..5e3d37bd62ac 100644 --- a/UPGRADING +++ b/UPGRADING @@ -38,10 +38,6 @@ PHP 8.6 UPGRADE NOTES types / values and raise a TypeError / ValueError accordingly. - Intl: - . IntlDateFormatter::parse()/datefmt_parse() and - IntlDateFormatter::localtime()/datefmt_localtime() now raise a TypeError - when the offset argument is not of type int instead of silently converting - the value. . Passing a non-stringable object as a time zone to Intl APIs that accept time zone objects or strings now raises a TypeError instead of an Error. . IntlBreakIterator::getLocale() now raises a ValueError when the type is @@ -57,6 +53,10 @@ PHP 8.6 UPGRADE NOTES . ResourceBundle::get() and resourcebundle_get() now report fallback-disabled resource lookups with "without fallback to " instead of the malformed "without fallback from to ". + . IntlDateFormatter::parse()/datefmt_parse() and + IntlDateFormatter::localtime()/datefmt_localtime() now raise a TypeError + when the offset argument is not of type int instead of silently converting + the value. - PCNTL: . pcntl_alarm() now raises a ValueError if the seconds argument is From ab4607b37ec0292d4b8b3e8f4368ad39a87b2585 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 2 Jul 2026 19:29:46 +0800 Subject: [PATCH 4/5] use l! instead of z! --- ext/intl/dateformat/dateformat_parse.cpp | 49 +++++++++++------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/ext/intl/dateformat/dateformat_parse.cpp b/ext/intl/dateformat/dateformat_parse.cpp index 0537b42feec5..d9b07867cce9 100644 --- a/ext/intl/dateformat/dateformat_parse.cpp +++ b/ext/intl/dateformat/dateformat_parse.cpp @@ -133,26 +133,25 @@ U_CFUNC PHP_FUNCTION(datefmt_parse) char* text_to_parse = NULL; size_t text_len =0; zval* z_parse_pos = NULL; + zend_long long_parse_pos = 0; + bool parse_pos_is_null = 1; int32_t parse_pos = -1; DATE_FORMAT_METHOD_INIT_VARS; /* Parse parameters. */ - if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|z!", - &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &z_parse_pos ) == FAILURE ){ + if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|l!", + &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &long_parse_pos, &parse_pos_is_null ) == FAILURE ){ RETURN_THROWS(); } + if (ZEND_NUM_ARGS() >= (hasThis() ? 2 : 3)) { + z_parse_pos = ZEND_CALL_ARG(execute_data, hasThis() ? 2 : 3); + } /* Fetch the object. */ DATE_FORMAT_METHOD_FETCH_OBJECT; - if (z_parse_pos) { - bool failed; - const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); - if (failed) { - zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); - RETURN_THROWS(); - } + if (!parse_pos_is_null) { if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range."); @@ -174,6 +173,8 @@ U_CFUNC PHP_METHOD(IntlDateFormatter, parseToCalendar) { zend_string *text_to_parse = NULL; zval* z_parse_pos = NULL; + zend_long long_parse_pos = 0; + bool parse_pos_is_null = 1; int32_t parse_pos = -1; DATE_FORMAT_METHOD_INIT_VARS; @@ -181,21 +182,18 @@ U_CFUNC PHP_METHOD(IntlDateFormatter, parseToCalendar) ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(text_to_parse) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(z_parse_pos) + Z_PARAM_LONG_EX(long_parse_pos, parse_pos_is_null, 1, 1) ZEND_PARSE_PARAMETERS_END(); + if (ZEND_NUM_ARGS() >= 2) { + z_parse_pos = ZEND_CALL_ARG(execute_data, 2); + } object = ZEND_THIS; /* Fetch the object. */ DATE_FORMAT_METHOD_FETCH_OBJECT; - if (z_parse_pos) { - bool failed; - const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); - if (failed) { - zend_argument_type_error(2, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); - RETURN_THROWS(); - } + if (!parse_pos_is_null) { if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range."); @@ -218,26 +216,25 @@ U_CFUNC PHP_FUNCTION(datefmt_localtime) char* text_to_parse = NULL; size_t text_len =0; zval* z_parse_pos = NULL; + zend_long long_parse_pos = 0; + bool parse_pos_is_null = 1; int32_t parse_pos = -1; DATE_FORMAT_METHOD_INIT_VARS; /* Parse parameters. */ - if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|z!", - &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &z_parse_pos ) == FAILURE ){ + if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|l!", + &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &long_parse_pos, &parse_pos_is_null ) == FAILURE ){ RETURN_THROWS(); } + if (ZEND_NUM_ARGS() >= (hasThis() ? 2 : 3)) { + z_parse_pos = ZEND_CALL_ARG(execute_data, hasThis() ? 2 : 3); + } /* Fetch the object. */ DATE_FORMAT_METHOD_FETCH_OBJECT; - if (z_parse_pos) { - bool failed; - const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); - if (failed) { - zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); - RETURN_THROWS(); - } + if (!parse_pos_is_null) { if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range."); From b67ca2f2d5f0355fe502d69cabe72162fcfe053b Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 2 Jul 2026 19:43:54 +0800 Subject: [PATCH 5/5] Revert "use l! instead of z!" This reverts commit ab4607b37ec0292d4b8b3e8f4368ad39a87b2585. --- ext/intl/dateformat/dateformat_parse.cpp | 49 +++++++++++++----------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/ext/intl/dateformat/dateformat_parse.cpp b/ext/intl/dateformat/dateformat_parse.cpp index d9b07867cce9..0537b42feec5 100644 --- a/ext/intl/dateformat/dateformat_parse.cpp +++ b/ext/intl/dateformat/dateformat_parse.cpp @@ -133,25 +133,26 @@ U_CFUNC PHP_FUNCTION(datefmt_parse) char* text_to_parse = NULL; size_t text_len =0; zval* z_parse_pos = NULL; - zend_long long_parse_pos = 0; - bool parse_pos_is_null = 1; int32_t parse_pos = -1; DATE_FORMAT_METHOD_INIT_VARS; /* Parse parameters. */ - if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|l!", - &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &long_parse_pos, &parse_pos_is_null ) == FAILURE ){ + if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|z!", + &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &z_parse_pos ) == FAILURE ){ RETURN_THROWS(); } - if (ZEND_NUM_ARGS() >= (hasThis() ? 2 : 3)) { - z_parse_pos = ZEND_CALL_ARG(execute_data, hasThis() ? 2 : 3); - } /* Fetch the object. */ DATE_FORMAT_METHOD_FETCH_OBJECT; - if (!parse_pos_is_null) { + if (z_parse_pos) { + bool failed; + const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); + if (failed) { + zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); + RETURN_THROWS(); + } if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range."); @@ -173,8 +174,6 @@ U_CFUNC PHP_METHOD(IntlDateFormatter, parseToCalendar) { zend_string *text_to_parse = NULL; zval* z_parse_pos = NULL; - zend_long long_parse_pos = 0; - bool parse_pos_is_null = 1; int32_t parse_pos = -1; DATE_FORMAT_METHOD_INIT_VARS; @@ -182,18 +181,21 @@ U_CFUNC PHP_METHOD(IntlDateFormatter, parseToCalendar) ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(text_to_parse) Z_PARAM_OPTIONAL - Z_PARAM_LONG_EX(long_parse_pos, parse_pos_is_null, 1, 1) + Z_PARAM_ZVAL(z_parse_pos) ZEND_PARSE_PARAMETERS_END(); - if (ZEND_NUM_ARGS() >= 2) { - z_parse_pos = ZEND_CALL_ARG(execute_data, 2); - } object = ZEND_THIS; /* Fetch the object. */ DATE_FORMAT_METHOD_FETCH_OBJECT; - if (!parse_pos_is_null) { + if (z_parse_pos) { + bool failed; + const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); + if (failed) { + zend_argument_type_error(2, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); + RETURN_THROWS(); + } if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range."); @@ -216,25 +218,26 @@ U_CFUNC PHP_FUNCTION(datefmt_localtime) char* text_to_parse = NULL; size_t text_len =0; zval* z_parse_pos = NULL; - zend_long long_parse_pos = 0; - bool parse_pos_is_null = 1; int32_t parse_pos = -1; DATE_FORMAT_METHOD_INIT_VARS; /* Parse parameters. */ - if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|l!", - &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &long_parse_pos, &parse_pos_is_null ) == FAILURE ){ + if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|z!", + &object, IntlDateFormatter_ce_ptr, &text_to_parse, &text_len, &z_parse_pos ) == FAILURE ){ RETURN_THROWS(); } - if (ZEND_NUM_ARGS() >= (hasThis() ? 2 : 3)) { - z_parse_pos = ZEND_CALL_ARG(execute_data, hasThis() ? 2 : 3); - } /* Fetch the object. */ DATE_FORMAT_METHOD_FETCH_OBJECT; - if (!parse_pos_is_null) { + if (z_parse_pos) { + bool failed; + const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed); + if (failed) { + zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos)); + RETURN_THROWS(); + } if (ZEND_LONG_INT_OVFL(long_parse_pos)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "String index is out of valid range.");