Commit 9f96285f2a8 for php.net

commit 9f96285f2a895a8c0a91d7a26ff1d6779ead2cb2
Author: Gina Peter Banyard <girgias@php.net>
Date:   Wed Jun 3 14:11:26 2026 +0100

    win32/signal.c: convert ctrl_handler to FCC (#22210)

diff --git a/sapi/cli/tests/sapi_windows_set_ctrl_trampoline.phpt b/sapi/cli/tests/sapi_windows_set_ctrl_trampoline.phpt
new file mode 100644
index 00000000000..4699387b2c3
--- /dev/null
+++ b/sapi/cli/tests/sapi_windows_set_ctrl_trampoline.phpt
@@ -0,0 +1,31 @@
+--TEST--
+sapi_windows_set_ctrl_handler() trampoline test
+--SKIPIF--
+<?php
+include "skipif.inc";
+
+if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')
+  die("skip this test is for Windows platforms only");
+?>
+--FILE--
+<?php
+
+class TrampolineTest {
+    public function __call(string $name, array $arguments) {
+        echo 'Trampoline for ', $name, PHP_EOL;
+    }
+}
+$o = new TrampolineTest();
+$callback = [$o, 'trampoline'];
+
+sapi_windows_set_ctrl_handler($callback);
+
+function foo(int $event) { }
+
+sapi_windows_set_ctrl_handler(foo(...));
+
+echo "Done\n";
+
+?>
+--EXPECT--
+Done
diff --git a/win32/signal.c b/win32/signal.c
index c04fe860437..abce3edb2fd 100644
--- a/win32/signal.c
+++ b/win32/signal.c
@@ -18,7 +18,7 @@
 #include "win32/console.h"

 /* true globals; only used from main thread and from kernel callback */
-static zval ctrl_handler;
+static zend_fcall_info_cache ctrl_handler;
 static DWORD ctrl_evt = (DWORD)-1;
 static zend_atomic_bool *vm_interrupt_flag = NULL;

@@ -26,14 +26,12 @@ static void (*orig_interrupt_function)(zend_execute_data *execute_data);

 static void php_win32_signal_ctrl_interrupt_function(zend_execute_data *execute_data)
 {/*{{{*/
-	if (IS_UNDEF != Z_TYPE(ctrl_handler)) {
-		zval retval, params[1];
+	if (ZEND_FCC_INITIALIZED(ctrl_handler)) {
+		zval params[1];

 		ZVAL_LONG(&params[0], ctrl_evt);

-		/* If the function returns, */
-		call_user_function(NULL, NULL, &ctrl_handler, &retval, 1, params);
-		zval_ptr_dtor(&retval);
+		zend_call_known_fcc(&ctrl_handler, NULL, 1, params, NULL);
 	}

 	if (orig_interrupt_function) {
@@ -51,7 +49,7 @@ PHP_WINUTIL_API void php_win32_signal_ctrl_handler_init(void)
 	orig_interrupt_function = zend_interrupt_function;
 	zend_interrupt_function = php_win32_signal_ctrl_interrupt_function;
 	vm_interrupt_flag = &EG(vm_interrupt);
-	ZVAL_UNDEF(&ctrl_handler);
+	ctrl_handler = empty_fcall_info_cache;

 	REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_EVENT_CTRL_C", CTRL_C_EVENT, CONST_PERSISTENT);
 	REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_EVENT_CTRL_BREAK", CTRL_BREAK_EVENT, CONST_PERSISTENT);
@@ -82,9 +80,8 @@ PHP_WINUTIL_API void php_win32_signal_ctrl_handler_request_shutdown(void)

 	/* The ctrl_handler must be cleared between requests, otherwise we can crash
 	 * due to accessing a previous request's memory. */
-	if (!Z_ISUNDEF(ctrl_handler)) {
-		zval_ptr_dtor(&ctrl_handler);
-		ZVAL_UNDEF(&ctrl_handler);
+	if (ZEND_FCC_INITIALIZED(ctrl_handler)) {
+		zend_fcc_dtor(&ctrl_handler);
 	}
 }

@@ -110,25 +107,32 @@ PHP_FUNCTION(sapi_windows_set_ctrl_handler)


 	/* callable argument corresponds to the CTRL handler */
-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "f!|b", &fci, &fcc, &add) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "F!|b", &fci, &fcc, &add) == FAILURE) {
 		RETURN_THROWS();
 	}

 #ifdef ZTS
 	if (!tsrm_is_main_thread()) {
+		if (ZEND_FCC_INITIALIZED(fcc)) {
+			zend_release_fcall_info_cache(&fcc);
+		}
 		zend_throw_error(NULL, "CTRL events can only be received on the main thread");
 		RETURN_THROWS();
 	}
 #endif

 	if (!php_win32_console_is_cli_sapi()) {
+		if (ZEND_FCC_INITIALIZED(fcc)) {
+			zend_release_fcall_info_cache(&fcc);
+		}
 		zend_throw_error(NULL, "CTRL events trapping is only supported on console");
 		RETURN_THROWS();
 	}

-	if (!ZEND_FCI_INITIALIZED(fci)) {
-		zval_ptr_dtor(&ctrl_handler);
-		ZVAL_UNDEF(&ctrl_handler);
+	if (!ZEND_FCC_INITIALIZED(fcc)) {
+		if (ZEND_FCC_INITIALIZED(ctrl_handler)) {
+			zend_fcc_dtor(&ctrl_handler);
+		}
 		RETURN_BOOL(SetConsoleCtrlHandler(NULL, add));
 	}

@@ -136,11 +140,14 @@ PHP_FUNCTION(sapi_windows_set_ctrl_handler)
 		zend_string *func_name = zend_get_callable_name(&fci.function_name);
 		php_error_docref(NULL, E_WARNING, "Unable to attach %s as a CTRL handler", ZSTR_VAL(func_name));
 		zend_string_release_ex(func_name, 0);
+		zend_release_fcall_info_cache(&fcc);
 		RETURN_FALSE;
 	}

-	zval_ptr_dtor(&ctrl_handler);
-	ZVAL_COPY(&ctrl_handler, &fci.function_name);
+	if (ZEND_FCC_INITIALIZED(ctrl_handler)) {
+		zend_fcc_dtor(&ctrl_handler);
+	}
+	zend_fcc_dup(&ctrl_handler, &fcc);

 	RETURN_TRUE;
 }/*}}}*/
@@ -163,7 +170,7 @@ PHP_FUNCTION(sapi_windows_generate_ctrl_event)

 	ret = (GenerateConsoleCtrlEvent(evt, pid) != 0);

-	if (IS_UNDEF != Z_TYPE(ctrl_handler)) {
+	if (ZEND_FCC_INITIALIZED(ctrl_handler)) {
 		ret = ret && SetConsoleCtrlHandler(php_win32_signal_system_ctrl_handler, TRUE);
 	}