Commit 2311816c9ef for php.net
commit 2311816c9ef728f0dae8539de27be5bfea678e9c
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sun Aug 16 12:59:01 2026 -0400
ext/intl: Resolve UConverter callbacks when the object is allocated
__construct filled to_cache/from_cache via php_converter_resolve_callback,
so a clone, which never runs the constructor, kept both FCCs zeroed and
the first callback-worthy convert called zend_call_known_fcc on a NULL
handler. Resolve both caches in php_converter_object_ctor so every
allocation path gets them.
Closes GH-23324
diff --git a/NEWS b/NEWS
index 9642ef64b09..511a899e527 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,8 @@ PHP NEWS
. Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state
such as dateType, timeType, calendar and the message pattern.
(Ilia Alshanetsky)
+ . Fixed a crash when converting with a cloned UConverter that uses
+ toUCallback/fromUCallback. (Ilia Alshanetsky)
- MBString:
. Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
diff --git a/ext/intl/converter/converter.c b/ext/intl/converter/converter.c
index 759db5e1887..bf89691dd6e 100644
--- a/ext/intl/converter/converter.c
+++ b/ext/intl/converter/converter.c
@@ -546,8 +546,6 @@ PHP_METHOD(UConverter, __construct) {
ZEND_ASSERT(EG(exception));
goto cleanup;
}
- php_converter_resolve_callback(&objval->to_cache, Z_OBJ_P(ZEND_THIS), ZEND_STRL("toUCallback"));
- php_converter_resolve_callback(&objval->from_cache, Z_OBJ_P(ZEND_THIS), ZEND_STRL("fromUCallback"));
cleanup:
INTL_G(use_exceptions) = old_use_exception;
INTL_G(error_level) = old_error_level;
@@ -916,6 +914,8 @@ static zend_object *php_converter_object_ctor(zend_class_entry *ce, php_converte
zend_object_std_init(&objval->obj, ce);
object_properties_init(&objval->obj, ce);
intl_error_init(&(objval->error));
+ php_converter_resolve_callback(&objval->to_cache, &objval->obj, ZEND_STRL("toUCallback"));
+ php_converter_resolve_callback(&objval->from_cache, &objval->obj, ZEND_STRL("fromUCallback"));
*pobjval = objval;
diff --git a/ext/intl/tests/uconverter_clone_callback.phpt b/ext/intl/tests/uconverter_clone_callback.phpt
new file mode 100644
index 00000000000..140841b455e
--- /dev/null
+++ b/ext/intl/tests/uconverter_clone_callback.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Cloned UConverter resolves toUCallback/fromUCallback on the clone
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+
+class MyConverter extends UConverter {
+ public int $hits = 0;
+
+ public function toUCallback($reason, $source, $codeUnits, &$error): string|int|array|null {
+ $this->hits++;
+ return parent::toUCallback($reason, $source, $codeUnits, $error);
+ }
+}
+
+$orig = new MyConverter('ascii', 'utf-8');
+$clone = clone $orig;
+$clone->convert("irregul\xC1\xA1r");
+echo $clone->hits > 0 ? "ok\n" : "no callback\n";
+
+?>
+--EXPECT--
+ok