Commit 305a60b0d2d for php.net

commit 305a60b0d2d3bbf2b2091e68199950c4d42b3739
Author: Weilin Du <weilindu@php.net>
Date:   Sun Sep 20 00:02:27 2026 +0800

    ext/intl: Optimize callback handling in IntlChar::enumCharTypes() (#23748)

    Just pass the FCC rather than a FCI/FCC pair is safe here.

    Co-authored-by: Gina Peter Banyard <girgias@php.net>

diff --git a/UPGRADING b/UPGRADING
index 5e019b44cd5..f2a2ef8f5e7 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -1111,6 +1111,7 @@ PHP 8.6 UPGRADE NOTES
     their returned arrays.
   . Improved performance of transliterator_list_ids() and
     resourcebundle_locales() by pre-allocating their returned arrays.
+  . Optimized callback invocation in IntlChar::enumCharTypes().

 - JSON:
   . Improve performance of encoding arrays and objects.
diff --git a/ext/intl/uchar/tests/enumCharTypes_trampoline.phpt b/ext/intl/uchar/tests/enumCharTypes_trampoline.phpt
new file mode 100644
index 00000000000..014493cb6b3
--- /dev/null
+++ b/ext/intl/uchar/tests/enumCharTypes_trampoline.phpt
@@ -0,0 +1,58 @@
+--TEST--
+IntlChar::enumCharTypes() with trampoline callbacks
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+function testTrampoline(string $name, array $arguments): bool {
+    // Only print the first two ranges to avoid ICU version dependencies.
+    if ($arguments[0] < 33) {
+        echo $name, ': ', implode(', ', $arguments), "\n";
+    }
+    if ($name === 'trampolineThrow' && $arguments[0] === 32) {
+        throw new Exception('Stop enumeration');
+    }
+    return false;
+}
+
+class TrampolineTest {
+    public function __call(string $name, array $arguments) {
+        return testTrampoline($name, $arguments);
+    }
+    public static function __callStatic(string $name, array $arguments) {
+        return testTrampoline($name, $arguments);
+    }
+}
+
+foreach ([new TrampolineTest(), TrampolineTest::class] as $target) {
+    echo is_object($target) ? "__call\n" : "__callStatic\n";
+    foreach (['trampoline', 'trampolineThrow', 'trampoline'] as $method) {
+        try {
+            var_dump(IntlChar::enumCharTypes([$target, $method]));
+        } catch (Exception $e) {
+            echo $e->getMessage(), "\n";
+        }
+    }
+}
+?>
+--EXPECT--
+__call
+trampoline: 0, 32, 15
+trampoline: 32, 33, 12
+NULL
+trampolineThrow: 0, 32, 15
+trampolineThrow: 32, 33, 12
+Stop enumeration
+trampoline: 0, 32, 15
+trampoline: 32, 33, 12
+NULL
+__callStatic
+trampoline: 0, 32, 15
+trampoline: 32, 33, 12
+NULL
+trampolineThrow: 0, 32, 15
+trampolineThrow: 32, 33, 12
+Stop enumeration
+trampoline: 0, 32, 15
+trampoline: 32, 33, 12
+NULL
diff --git a/ext/intl/uchar/uchar.cpp b/ext/intl/uchar/uchar.cpp
index 89bc0a31733..1319ec67908 100644
--- a/ext/intl/uchar/uchar.cpp
+++ b/ext/intl/uchar/uchar.cpp
@@ -188,16 +188,11 @@ IC_METHOD(getNumericValue) {
 /* }}} */

 /* {{{ */
-typedef struct _enumCharType_data {
-	zend_fcall_info fci;
-	zend_fcall_info_cache fci_cache;
-} enumCharType_data;
-static UBool enumCharType_callback(enumCharType_data *context,
+static UBool enumCharType_callback(const void *context,
 		UChar32 start, UChar32 limit, UCharCategory type) {
-	zval retval;
+	const zend_fcall_info_cache *fcc = static_cast<const zend_fcall_info_cache *>(context);
 	zval args[3];

-	ZVAL_NULL(&retval);
 	/* Note that $start is INclusive, while $limit is EXclusive
 	 * Therefore (0, 32, 15) means CPs 0..31 are of type 15
 	 */
@@ -205,27 +200,19 @@ static UBool enumCharType_callback(enumCharType_data *context,
 	ZVAL_LONG(&args[1], limit);
 	ZVAL_LONG(&args[2], type);

-	context->fci.retval = &retval;
-	context->fci.param_count = 3;
-	context->fci.params = args;
-
-	if (zend_call_function(&context->fci, &context->fci_cache) == FAILURE) {
-		intl_error_set_code(NULL, U_INTERNAL_PROGRAM_ERROR);
-		intl_errors_set_custom_msg(NULL, "enumCharTypes callback failed");
-		zval_ptr_dtor(&retval);
-		return 0;
-	}
-	zval_ptr_dtor(&retval);
-	return 1;
+	zend_call_known_fcc(fcc, NULL, 3, args, NULL);
+	return !EG(exception);
 }
 IC_METHOD(enumCharTypes) {
-	enumCharType_data context;
+	zend_fcall_info fci;
+	zend_fcall_info_cache fcc;


 	ZEND_PARSE_PARAMETERS_START(1, 1)
-		Z_PARAM_FUNC(context.fci, context.fci_cache)
+		Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc)
 	ZEND_PARSE_PARAMETERS_END();
-	u_enumCharTypes((UCharEnumTypeRange*)enumCharType_callback, &context);
+	u_enumCharTypes(enumCharType_callback, &fcc);
+	zend_release_fcall_info_cache(&fcc);
 }
 /* }}} */