From 0f20be38672a08a3ebed8f4492f4a8e1872fd3f0 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 24 Aug 2026 11:44:06 -0400 Subject: [PATCH 1/2] ext/intl: Preserve PHP-side state when cloning formatters IntlDateFormatter_object_clone() left date_type, time_type, calendar and requested_locale at their constructor defaults and MessageFormatter_object_ clone() dropped orig_format/orig_format_len/tz_set, so a cloned formatter reported wrong types/calendar/pattern and lost the requested locale used by datefmt_set_calendar(). The clone handlers now copy these fields alongside the ICU handle; msgformat_data.arg_types is deliberately not copied since it is a lazily rebuilt cache derived from the cloned ICU formatter. Sibling audit: NumberFormatter, IntlCalendar, SpoofChecker and Transliterator carry no other PHP-side scalar state in their clone paths. Closes GH-23652 --- NEWS | 5 +++ ext/intl/dateformat/dateformat_class.c | 7 ++++ ext/intl/msgformat/msgformat_class.c | 6 +++ .../tests/clone_preserves_php_fields.phpt | 37 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 ext/intl/tests/clone_preserves_php_fields.phpt diff --git a/NEWS b/NEWS index c367688672c9..77689d91498d 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,11 @@ PHP NEWS registrations are freed while still reachable from the cycle collector. (Ilia Alshanetsky) +- Intl: + . Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state + such as dateType, timeType, calendar and the message pattern. + (Ilia Alshanetsky) + - MBString: . Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in a truncated UTF-8 sequence). (Lazizbek Ergashev) diff --git a/ext/intl/dateformat/dateformat_class.c b/ext/intl/dateformat/dateformat_class.c index 15bf5bf23ce5..21bb16dcd46b 100644 --- a/ext/intl/dateformat/dateformat_class.c +++ b/ext/intl/dateformat/dateformat_class.c @@ -71,6 +71,13 @@ zend_object *IntlDateFormatter_object_clone(zend_object *object) /* clone standard parts */ zend_objects_clone_members(&new_dfo->zo, &dfo->zo); + new_dfo->date_type = dfo->date_type; + new_dfo->time_type = dfo->time_type; + new_dfo->calendar = dfo->calendar; + if (dfo->requested_locale != NULL) { + new_dfo->requested_locale = estrdup(dfo->requested_locale); + } + /* clone formatter object */ if (DATE_FORMAT_OBJECT(dfo) != NULL) { UErrorCode error = U_ZERO_ERROR; diff --git a/ext/intl/msgformat/msgformat_class.c b/ext/intl/msgformat/msgformat_class.c index 4e0766a911b9..0d5097e481d0 100644 --- a/ext/intl/msgformat/msgformat_class.c +++ b/ext/intl/msgformat/msgformat_class.c @@ -63,6 +63,12 @@ zend_object *MessageFormatter_object_clone(zend_object *object) /* clone standard parts */ zend_objects_clone_members(&new_mfo->zo, &mfo->zo); + if (mfo->mf_data.orig_format != NULL) { + new_mfo->mf_data.orig_format = estrndup(mfo->mf_data.orig_format, mfo->mf_data.orig_format_len); + new_mfo->mf_data.orig_format_len = mfo->mf_data.orig_format_len; + } + new_mfo->mf_data.tz_set = mfo->mf_data.tz_set; + /* clone formatter object */ if (MSG_FORMAT_OBJECT(mfo) != NULL) { UErrorCode error = U_ZERO_ERROR; diff --git a/ext/intl/tests/clone_preserves_php_fields.phpt b/ext/intl/tests/clone_preserves_php_fields.phpt new file mode 100644 index 000000000000..1c57ce539ad6 --- /dev/null +++ b/ext/intl/tests/clone_preserves_php_fields.phpt @@ -0,0 +1,37 @@ +--TEST-- +Cloning IntlDateFormatter and MessageFormatter preserves PHP-side fields +--EXTENSIONS-- +intl +--FILE-- +getDateType()); +var_dump($c->getTimeType()); +var_dump($c->getCalendar()); +var_dump($c->format(strtotime('2024-01-02 03:04:05 UTC'))); +$c->setCalendar(IntlDateFormatter::GREGORIAN); +var_dump($c->getPattern()); + +$m = new MessageFormatter('en_US', '{0, number}'); +$mc = clone $m; +var_dump($mc->getPattern()); +var_dump($mc->format([1.5])); + +date_default_timezone_set('UTC'); +$t = new MessageFormatter('en_US', '{0,time,short}'); +$dt = new DateTimeImmutable('2024-01-02 03:04:05', new DateTimeZone('UTC')); +$t->format([$dt]); +$tc = clone $t; +date_default_timezone_set('America/New_York'); +var_dump($t->format([$dt]) === $tc->format([$dt])); +?> +--EXPECT-- +int(1) +int(2) +int(1) +string(26) "2. Januar 2024 um 03:04:05" +string(23) "d. MMMM y 'um' HH:mm:ss" +string(11) "{0, number}" +string(3) "1.5" +bool(true) From 11da2ee255fd04c1cd891f6fa7c2c4501a1c9060 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 13 Sep 2026 10:13:58 -0400 Subject: [PATCH 2/2] TSRM: roll back id_count when the resource type table cannot grow (#23595) ts_allocate_id(), ts_allocate_fast_id_at() and ts_allocate_tls_id() increment id_count before growing resource_types_table. When the realloc fails they return 0 with id_count still counting the new slot, so allocate_new_resource() later walks j < id_count and reads an entry that was never initialized. Roll id_count back on those three paths. Closes GH-23595 --- TSRM/TSRM.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c index a5032e456aae..a9f6ed5c59bb 100644 --- a/TSRM/TSRM.c +++ b/TSRM/TSRM.c @@ -305,6 +305,7 @@ TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate _tmp = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); if (!_tmp) { TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource")); + id_count--; *rsrc_id = 0; tsrm_mutex_unlock(tsmm_mutex); return 0; @@ -384,6 +385,7 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, _tmp = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); if (!_tmp) { TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource")); + id_count--; *rsrc_id = 0; tsrm_mutex_unlock(tsmm_mutex); return 0; @@ -419,6 +421,7 @@ TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(vo _tmp = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); if (!_tmp) { TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource")); + id_count--; *rsrc_id = 0; tsrm_mutex_unlock(tsmm_mutex); return 0;