diff --git a/NEWS b/NEWS index e7dd84771b7f3..0e4a16cf6e8a6 100644 --- a/NEWS +++ b/NEWS @@ -62,6 +62,13 @@ PHP NEWS . Added IntlDateFormatter::getIanaID/intltz_get_iana_id method/function. (David Carlier) . Set to C++17 standard for icu 74 and onwards. (David Carlier) + . resourcebundle_get(), ResourceBundle::get(), and accessing offsets on a + ResourceBundle object now throw: + - TypeError for invalid offset types + - ValueError for an empty string + - ValueError if the integer index does not fit in a signed 32 bit integer + . ResourceBundle::get() now has a tentative return type of: + ResourceBundle|array|string|int|null - LDAP: . Added LDAP_OPT_X_TLS_PROTOCOL_MAX/LDAP_OPT_X_TLS_PROTOCOL_TLS1_3 diff --git a/UPGRADING b/UPGRADING index 0a1bc04343bcd..3b080ac78444d 100644 --- a/UPGRADING +++ b/UPGRADING @@ -45,6 +45,13 @@ PHP 8.4 UPGRADE NOTES object. This is no longer possible, and cloning a DOMXPath object now throws an error. +- Intl: + . resourcebundle_get(), ResourceBundle::get(), and accessing offsets on a + ResourceBundle object now throw: + - TypeError for invalid offset types + - ValueError for an empty string + - ValueError if the integer index does not fit in a signed 32 bit integer + - MBString: . mb_encode_numericentity() and mb_decode_numericentity() now check that the $map is only composed of integers, if not a ValueError is thrown. @@ -321,6 +328,8 @@ PHP 8.4 UPGRADE NOTES RFC: https://wiki.php.net/rfc/new_rounding_modes_to_round_function . NumberFormatter::ROUND_HALFODD added to complement existing NumberFormatter::ROUND_HALFEVEN functionality. + . ResourceBundle::get() now has a tentative return type of: + ResourceBundle|array|string|int|null - MBString: . The behavior of mb_strcut is more consistent now on invalid UTF-8 and UTF-16 diff --git a/ext/intl/php_intl.stub.php b/ext/intl/php_intl.stub.php index b2383fed10df3..50da8e32bd801 100644 --- a/ext/intl/php_intl.stub.php +++ b/ext/intl/php_intl.stub.php @@ -543,8 +543,7 @@ function normalizer_get_raw_decomposition(string $string, int $form = Normalizer function resourcebundle_create(?string $locale, ?string $bundle, bool $fallback = true): ?ResourceBundle {} -/** @param string|int $index */ -function resourcebundle_get(ResourceBundle $bundle, $index, bool $fallback = true): mixed {} +function resourcebundle_get(ResourceBundle $bundle, string|int $index, bool $fallback = true): ResourceBundle|array|string|int|null {} function resourcebundle_count(ResourceBundle $bundle): int {} diff --git a/ext/intl/php_intl_arginfo.h b/ext/intl/php_intl_arginfo.h index f26d802a7eb4e..b21dbe3ea4663 100644 --- a/ext/intl/php_intl_arginfo.h +++ b/ext/intl/php_intl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 50cc9edef7f40e6885be38be25a71f66d7405a50 */ + * Stub hash: c1adcd8a928af82766ee8c0cd6b20c2f2e9afcc1 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_intlcal_create_instance, 0, 0, IntlCalendar, 1) ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, timezone, "null") @@ -630,9 +630,9 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_resourcebundle_create, 0, 2, Reso ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fallback, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_resourcebundle_get, 0, 2, IS_MIXED, 0) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_resourcebundle_get, 0, 2, ResourceBundle, MAY_BE_ARRAY|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_NULL) ZEND_ARG_OBJ_INFO(0, bundle, ResourceBundle, 0) - ZEND_ARG_INFO(0, index) + ZEND_ARG_TYPE_MASK(0, index, MAY_BE_STRING|MAY_BE_LONG, NULL) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fallback, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() diff --git a/ext/intl/resourcebundle/resourcebundle.c b/ext/intl/resourcebundle/resourcebundle.c index 5b150bcb42a3e..5535ddd29a6b7 100644 --- a/ext/intl/resourcebundle/resourcebundle.c +++ b/ext/intl/resourcebundle/resourcebundle.c @@ -71,11 +71,7 @@ void resourcebundle_extract_value( zval *return_value, ResourceBundle_object *so source->child = NULL; intl_errors_reset(INTL_DATA_ERROR_P(source)); break; - - default: - intl_errors_set(INTL_DATA_ERROR_P(source), U_ILLEGAL_ARGUMENT_ERROR, "Unknown resource type", 0); - RETURN_FALSE; - break; + EMPTY_SWITCH_DEFAULT_CASE(); } } /* }}} */ diff --git a/ext/intl/resourcebundle/resourcebundle.stub.php b/ext/intl/resourcebundle/resourcebundle.stub.php index 47df7a19b3a9f..a768cf91cfe41 100644 --- a/ext/intl/resourcebundle/resourcebundle.stub.php +++ b/ext/intl/resourcebundle/resourcebundle.stub.php @@ -13,12 +13,8 @@ public function __construct(?string $locale, ?string $bundle, bool $fallback = t */ public static function create(?string $locale, ?string $bundle, bool $fallback = true): ?ResourceBundle {} - /** - * @param string|int $index - * @tentative-return-type - * @alias resourcebundle_get - */ - public function get($index, bool $fallback = true): mixed {} + /** @tentative-return-type */ + public function get(string|int $index, bool $fallback = true): ResourceBundle|array|string|int|null {} /** * @tentative-return-type diff --git a/ext/intl/resourcebundle/resourcebundle_arginfo.h b/ext/intl/resourcebundle/resourcebundle_arginfo.h index 8a338a466e866..bfbb3511e62bc 100644 --- a/ext/intl/resourcebundle/resourcebundle_arginfo.h +++ b/ext/intl/resourcebundle/resourcebundle_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7816536650d8513ef6998233096b0bf6a29d7af4 */ + * Stub hash: e302e5ca1abcb9b52e3c14abbd38b9e8f1461390 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ResourceBundle___construct, 0, 0, 2) ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 1) @@ -13,8 +13,8 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_ResourceBundle_cr ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fallback, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ResourceBundle_get, 0, 1, IS_MIXED, 0) - ZEND_ARG_INFO(0, index) +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_ResourceBundle_get, 0, 1, ResourceBundle, MAY_BE_ARRAY|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_NULL) + ZEND_ARG_TYPE_MASK(0, index, MAY_BE_STRING|MAY_BE_LONG, NULL) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fallback, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() @@ -35,7 +35,7 @@ ZEND_END_ARG_INFO() ZEND_METHOD(ResourceBundle, __construct); ZEND_FUNCTION(resourcebundle_create); -ZEND_FUNCTION(resourcebundle_get); +ZEND_METHOD(ResourceBundle, get); ZEND_FUNCTION(resourcebundle_count); ZEND_FUNCTION(resourcebundle_locales); ZEND_FUNCTION(resourcebundle_get_error_code); @@ -45,7 +45,7 @@ ZEND_METHOD(ResourceBundle, getIterator); static const zend_function_entry class_ResourceBundle_methods[] = { ZEND_ME(ResourceBundle, __construct, arginfo_class_ResourceBundle___construct, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("create", zif_resourcebundle_create, arginfo_class_ResourceBundle_create, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) - ZEND_RAW_FENTRY("get", zif_resourcebundle_get, arginfo_class_ResourceBundle_get, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_ME(ResourceBundle, get, arginfo_class_ResourceBundle_get, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("count", zif_resourcebundle_count, arginfo_class_ResourceBundle_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getLocales", zif_resourcebundle_locales, arginfo_class_ResourceBundle_getLocales, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getErrorCode", zif_resourcebundle_get_error_code, arginfo_class_ResourceBundle_getErrorCode, ZEND_ACC_PUBLIC, NULL, NULL) diff --git a/ext/intl/resourcebundle/resourcebundle_class.c b/ext/intl/resourcebundle/resourcebundle_class.c index 9e86b41e761a2..e5a88b0590b33 100644 --- a/ext/intl/resourcebundle/resourcebundle_class.c +++ b/ext/intl/resourcebundle/resourcebundle_class.c @@ -168,84 +168,134 @@ PHP_FUNCTION( resourcebundle_create ) /* }}} */ /* {{{ resourcebundle_array_fetch */ -static void resourcebundle_array_fetch(zend_object *object, zval *offset, zval *return_value, int fallback) +static zval *resource_bundle_array_fetch( + zend_object *object, zend_string *offset_str, zend_long offset_int, + zval *return_value, bool fallback, uint32_t offset_arg_num) { - int32_t meindex = 0; - char * mekey = NULL; - bool is_numeric = 0; - char *pbuf; + int32_t index = 0; + char *key = NULL; + bool is_numeric = offset_str == NULL; + char *pbuf; ResourceBundle_object *rb; rb = php_intl_resourcebundle_fetch_object(object); intl_error_reset(NULL); intl_error_reset(INTL_DATA_ERROR_P(rb)); - if(Z_TYPE_P(offset) == IS_LONG) { - is_numeric = 1; - meindex = (int32_t)Z_LVAL_P(offset); - rb->child = ures_getByIndex( rb->me, meindex, rb->child, &INTL_DATA_ERROR_CODE(rb) ); - } else if(Z_TYPE_P(offset) == IS_STRING) { - mekey = Z_STRVAL_P(offset); - rb->child = ures_getByKey(rb->me, mekey, rb->child, &INTL_DATA_ERROR_CODE(rb) ); + if (offset_str) { + if (UNEXPECTED(ZSTR_LEN(offset_str) == 0)) { + if (offset_arg_num) { + zend_argument_value_error(offset_arg_num, "cannot be empty"); + } else { + zend_value_error("Offset cannot be empty"); + } + return NULL; + } + key = ZSTR_VAL(offset_str); + rb->child = ures_getByKey(rb->me, key, rb->child, &INTL_DATA_ERROR_CODE(rb) ); } else { - intl_errors_set(INTL_DATA_ERROR_P(rb), U_ILLEGAL_ARGUMENT_ERROR, - "resourcebundle_get: index should be integer or string", 0); - RETURN_NULL(); + if (UNEXPECTED(offset_int < (zend_long)INT32_MIN || offset_int > (zend_long)INT32_MAX)) { + if (offset_arg_num) { + zend_argument_value_error(offset_arg_num, "index must be between %d and %d", INT32_MIN, INT32_MAX); + } else { + zend_value_error("Index must be between %d and %d", INT32_MIN, INT32_MAX); + } + return NULL; + } + index = (int32_t)offset_int; + rb->child = ures_getByIndex(rb->me, index, rb->child, &INTL_DATA_ERROR_CODE(rb)); } intl_error_set_code( NULL, INTL_DATA_ERROR_CODE(rb) ); if (U_FAILURE(INTL_DATA_ERROR_CODE(rb))) { if (is_numeric) { - spprintf( &pbuf, 0, "Cannot load resource element %d", meindex ); + spprintf( &pbuf, 0, "Cannot load resource element %d", index ); } else { - spprintf( &pbuf, 0, "Cannot load resource element '%s'", mekey ); + spprintf( &pbuf, 0, "Cannot load resource element '%s'", key ); } intl_errors_set_custom_msg( INTL_DATA_ERROR_P(rb), pbuf, 1 ); efree(pbuf); - RETURN_NULL(); + RETVAL_NULL(); + return return_value; } if (!fallback && (INTL_DATA_ERROR_CODE(rb) == U_USING_FALLBACK_WARNING || INTL_DATA_ERROR_CODE(rb) == U_USING_DEFAULT_WARNING)) { UErrorCode icuerror; const char * locale = ures_getLocaleByType( rb->me, ULOC_ACTUAL_LOCALE, &icuerror ); if (is_numeric) { - spprintf( &pbuf, 0, "Cannot load element %d without fallback from to %s", meindex, locale ); + spprintf(&pbuf, 0, "Cannot load element %d without fallback from to %s", index, locale); } else { - spprintf( &pbuf, 0, "Cannot load element '%s' without fallback from to %s", mekey, locale ); + spprintf(&pbuf, 0, "Cannot load element '%s' without fallback from to %s", key, locale); } - intl_errors_set_custom_msg( INTL_DATA_ERROR_P(rb), pbuf, 1 ); + intl_errors_set_custom_msg( INTL_DATA_ERROR_P(rb), pbuf, 1); efree(pbuf); - RETURN_NULL(); + RETVAL_NULL(); + return return_value; } resourcebundle_extract_value( return_value, rb ); + return return_value; } /* }}} */ /* {{{ resourcebundle_array_get */ zval *resourcebundle_array_get(zend_object *object, zval *offset, int type, zval *rv) { - if(offset == NULL) { - php_error( E_ERROR, "Cannot apply [] to ResourceBundle object" ); + if (offset == NULL) { + zend_throw_error(NULL, "Cannot apply [] to ResourceBundle object"); + return NULL; + } + + ZVAL_DEREF(offset); + if (Z_TYPE_P(offset) == IS_LONG) { + return resource_bundle_array_fetch(object, /* offset_str */ NULL, Z_LVAL_P(offset), rv, /* fallback */ true, /* arg_num */ 0); + } else if (Z_TYPE_P(offset) == IS_STRING) { + return resource_bundle_array_fetch(object, Z_STR_P(offset), /* offset_int */ 0, rv, /* fallback */ true, /* arg_num */ 0); + } else { + zend_illegal_container_offset(object->ce->name, offset, type); + return NULL; } - ZVAL_NULL(rv); - resourcebundle_array_fetch(object, offset, rv, 1); - return rv; } /* }}} */ /* {{{ Get resource identified by numerical index or key name. */ PHP_FUNCTION( resourcebundle_get ) { - bool fallback = 1; - zval * offset; - zval * object; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Oz|b", &object, ResourceBundle_ce_ptr, &offset, &fallback ) == FAILURE) { + bool fallback = true; + zend_object *resource_bundle = NULL; + zend_string *offset_str = NULL; + zend_long offset_long = 0; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_OBJ_OF_CLASS(resource_bundle, ResourceBundle_ce_ptr) + Z_PARAM_STR_OR_LONG(offset_str, offset_long) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(fallback) + ZEND_PARSE_PARAMETERS_END(); + + zval *retval = resource_bundle_array_fetch(resource_bundle, offset_str, offset_long, return_value, fallback, /* arg_num */ 2); + if (!retval) { RETURN_THROWS(); } +} +/* }}} */ - resourcebundle_array_fetch(Z_OBJ_P(object), offset, return_value, fallback); +PHP_METHOD(ResourceBundle , get) +{ + bool fallback = true; + zend_string *offset_str = NULL; + zend_long offset_long = 0; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR_OR_LONG(offset_str, offset_long) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(fallback) + ZEND_PARSE_PARAMETERS_END(); + + zval *retval = resource_bundle_array_fetch(Z_OBJ_P(ZEND_THIS), offset_str, offset_long, return_value, fallback, /* arg_num */ 1); + if (!retval) { + RETURN_THROWS(); + } } /* }}} */ diff --git a/ext/intl/tests/resourcebundle_arrayaccess.phpt b/ext/intl/tests/resourcebundle_arrayaccess.phpt index b41b5408cf696..49ad46fbe3509 100644 --- a/ext/intl/tests/resourcebundle_arrayaccess.phpt +++ b/ext/intl/tests/resourcebundle_arrayaccess.phpt @@ -23,6 +23,11 @@ intl $r2 = $r['testarray']; printf( "testarray: %s\n", $r2[2] ); + echo "Using a reference as an offset:\n"; + $offset = 'teststring'; + $ref = &$offset; + var_dump($r[$ref]); + $t = $r['nonexisting']; echo debug( $t ); ?> @@ -46,5 +51,7 @@ Array testbin: a1b2c3d4e5f67890 testtable: 3 testarray: string 3 +Using a reference as an offset: +string(12) "Hello World!" NULL 2: Cannot load resource element 'nonexisting': U_MISSING_RESOURCE_ERROR diff --git a/ext/intl/tests/resourcebundle_dimension_errors.phpt b/ext/intl/tests/resourcebundle_dimension_errors.phpt new file mode 100644 index 0000000000000..77f30262008ad --- /dev/null +++ b/ext/intl/tests/resourcebundle_dimension_errors.phpt @@ -0,0 +1,61 @@ +--TEST-- +Test ResourceBundle errors with [] +--EXTENSIONS-- +intl +--FILE-- +getMessage(), PHP_EOL; +} +try { + var_dump(isset($r['non-existent'])); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($r['non-existent'] ?? "default"); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($r[12.5]); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($r[new stdClass()]); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($r['']); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + /* This can only happen on 64bit */ + if (PHP_INT_SIZE > 4) { + var_dump($r[0xFFFFFFFFF]); + } else { + echo 'ValueError: Index must be between -2147483648 and 2147483647', PHP_EOL; + } +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Error: Cannot apply [] to ResourceBundle object +Error: Cannot use object of type ResourceBundle as array +string(7) "default" +TypeError: Cannot access offset of type float on ResourceBundle +TypeError: Cannot access offset of type stdClass on ResourceBundle +ValueError: Offset cannot be empty +ValueError: Index must be between -2147483648 and 2147483647