Commit 1805822555e for php.net
commit 1805822555e38f6b75eb74be7c8b6856e3eb3d10
Author: Gina Peter Banyard <girgias@php.net>
Date: Sun Jul 26 12:42:35 2026 +0100
sapi: only use FCC for header_register_callback() (#22877)
No need to rederive callability of zval, or copies of it.
diff --git a/main/SAPI.c b/main/SAPI.c
index 58e85539d54..523759ea79b 100644
--- a/main/SAPI.c
+++ b/main/SAPI.c
@@ -111,51 +111,25 @@ PHP_FUNCTION(header_register_callback)
zend_fcall_info fci;
zend_fcall_info_cache fcc;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "f", &fci, &fcc) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "F", &fci, &fcc) == FAILURE) {
RETURN_THROWS();
}
- if (Z_TYPE(SG(callback_func)) != IS_UNDEF) {
- zval_ptr_dtor(&SG(callback_func));
- SG(fci_cache) = empty_fcall_info_cache;
+ if (ZEND_FCC_INITIALIZED(SG(send_header_fcc))) {
+ zend_fcc_dtor(&SG(send_header_fcc));
}
/* Don't store callback if headers have already been sent:
* It won't get used and we won't have a chance to release it. */
- if (!SG(headers_sent)) {
- ZVAL_COPY(&SG(callback_func), &fci.function_name);
+ if (UNEXPECTED(SG(headers_sent))) {
+ zend_release_fcall_info_cache(&fcc);
+ } else {
+ zend_fcc_dup(&SG(send_header_fcc), &fcc);
}
-
RETURN_TRUE;
}
/* }}} */
-static void sapi_run_header_callback(zval *callback)
-{
- int error;
- zend_fcall_info fci;
- char *callback_error = NULL;
- zval retval;
-
- if (zend_fcall_info_init(callback, 0, &fci, &SG(fci_cache), NULL, &callback_error) == SUCCESS) {
- fci.retval = &retval;
-
- error = zend_call_function(&fci, &SG(fci_cache));
- if (error == FAILURE) {
- goto callback_failed;
- } else {
- zval_ptr_dtor(&retval);
- }
- } else {
-callback_failed:
- php_error_docref(NULL, E_WARNING, "Could not call the sapi_header_callback");
- }
-
- if (callback_error) {
- efree(callback_error);
- }
-}
-
SAPI_API void sapi_handle_post(void *arg)
{
if (SG(request_info).post_entry && SG(request_info).content_type_dup) {
@@ -436,7 +410,6 @@ SAPI_API void sapi_activate(void)
SG(sapi_headers).http_status_line = NULL;
SG(sapi_headers).mimetype = NULL;
SG(headers_sent) = 0;
- ZVAL_UNDEF(&SG(callback_func));
SG(read_post_bytes) = 0;
SG(request_info).request_body = NULL;
SG(request_info).current_user = NULL;
@@ -446,6 +419,7 @@ SAPI_API void sapi_activate(void)
SG(request_info).proto_num = 1000; /* Default to HTTP 1.0 */
SG(global_request_time) = 0;
SG(post_read) = 0;
+ SG(send_header_fcc) = empty_fcall_info_cache;
/* It's possible to override this general case in the activate() callback, if necessary. */
if (SG(request_info).request_method && !strcmp(SG(request_info).request_method, "HEAD")) {
SG(request_info).headers_only = 1;
@@ -889,12 +863,12 @@ SAPI_API int sapi_send_headers(void)
SG(sapi_headers).send_default_content_type = 0;
}
- if (Z_TYPE(SG(callback_func)) != IS_UNDEF) {
- zval cb;
- ZVAL_COPY_VALUE(&cb, &SG(callback_func));
- ZVAL_UNDEF(&SG(callback_func));
- sapi_run_header_callback(&cb);
- zval_ptr_dtor(&cb);
+ if (ZEND_FCC_INITIALIZED(SG(send_header_fcc))) {
+ zend_fcall_info_cache fcc = SG(send_header_fcc);
+ /* Prevent triggering the callback multiple times */
+ SG(send_header_fcc) = empty_fcall_info_cache;
+ zend_call_known_fcc(&fcc, NULL, 0, NULL, NULL);
+ zend_fcc_dtor(&fcc);
}
SG(headers_sent) = 1;
diff --git a/main/SAPI.h b/main/SAPI.h
index e62f686603c..d34c12f1bbc 100644
--- a/main/SAPI.h
+++ b/main/SAPI.h
@@ -143,8 +143,7 @@ typedef struct _sapi_globals_struct {
bool sapi_started;
double global_request_time;
HashTable known_post_content_types;
- zval callback_func;
- zend_fcall_info_cache fci_cache;
+ zend_fcall_info_cache send_header_fcc;
sapi_request_parse_body_context request_parse_body_context;
} sapi_globals_struct;
diff --git a/tests/basic/header_register_callback_trampoline.phpt b/tests/basic/header_register_callback_trampoline.phpt
new file mode 100644
index 00000000000..cc4a1e4014e
--- /dev/null
+++ b/tests/basic/header_register_callback_trampoline.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test header_register_callback
+--FILE--
+<?php
+class TrampolineTest {
+ public function __call(string $name, array $arguments) {
+ echo 'Trampoline for ', $name, PHP_EOL;
+ }
+}
+$o = new TrampolineTest();
+$callback = [$o, 'trampoline'];
+
+header_register_callback($callback);
+?>
+--EXPECT--
+Trampoline for trampoline
diff --git a/tests/basic/header_register_callback_trampoline_after_headers_sent.phpt b/tests/basic/header_register_callback_trampoline_after_headers_sent.phpt
new file mode 100644
index 00000000000..7f343c6d813
--- /dev/null
+++ b/tests/basic/header_register_callback_trampoline_after_headers_sent.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test header_register_callback
+--FILE--
+<?php
+class TrampolineTest {
+ public function __call(string $name, array $arguments) {
+ echo 'Trampoline for ', $name, PHP_EOL;
+ }
+}
+$o = new TrampolineTest();
+$callback = [$o, 'trampoline'];
+
+echo "Send headers.\n";
+header_register_callback($callback);
+?>
+--EXPECT--
+Send headers.