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