Commit cd383dcc377 for php
commit cd383dcc3770d4307755d4ac2f5887e4d8faa830
Author: Gina Peter Banyard <girgias@php.net>
Date: Mon Oct 12 13:46:38 2026 +0100
pcre: only use FCC for preg_replace_callback functions (#24152)
Rather than resolving trampolines at every call we can simplify increase the refcount for the FCC.
diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c
index fce1a21a6c2..256b67e9e4c 100644
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@ -1553,7 +1553,7 @@ static bool preg_get_backref(char **str, int *backref)
}
/* Return NULL if an exception has occurred */
-static zend_string *preg_do_repl_func(zend_fcall_info *fci, zend_fcall_info_cache *fcc, const zend_string *subject, PCRE2_SIZE *offsets, zend_string **subpat_names, uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags)
+static zend_string *preg_do_repl_func(zend_fcall_info_cache *fcc, const zend_string *subject, PCRE2_SIZE *offsets, zend_string **subpat_names, uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags)
{
zend_string *result_str = NULL;
zval retval; /* Function return value */
@@ -1562,11 +1562,7 @@ static zend_string *preg_do_repl_func(zend_fcall_info *fci, zend_fcall_info_cach
array_init_size(&arg, count + (mark ? 1 : 0));
populate_subpat_array(Z_ARRVAL(arg), ZSTR_VAL(subject), offsets, subpat_names, num_subpats, count, mark, flags);
- fci->retval = &retval;
- fci->param_count = 1;
- fci->params = &arg;
- fci->consumed_args = zend_fci_consumed_arg(0);
- zend_call_function(fci, fcc);
+ zend_call_known_fcc_ex(fcc, &retval, 1, &arg, NULL, zend_fci_consumed_arg(0));
zval_ptr_dtor(&arg);
if (EXPECTED(Z_TYPE(retval) == IS_STRING)) {
return Z_STR(retval);
@@ -1854,7 +1850,7 @@ PHPAPI zend_string *php_pcre_replace_impl(const pcre_cache_entry *pce, zend_stri
/* }}} */
static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_string *subject_str,
- zend_fcall_info *fci, zend_fcall_info_cache *fcc,
+ zend_fcall_info_cache *fcc,
size_t limit, size_t *replace_count, zend_long flags
) {
uint32_t options; /* Execution options */
@@ -1952,7 +1948,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
/* Use custom function to get replacement string and its length. */
zend_string *eval_result = preg_do_repl_func(
- fci, fcc, subject_str, offsets, subpat_names, num_subpats, count,
+ fcc, subject_str, offsets, subpat_names, num_subpats, count,
pcre2_get_mark(match_data), flags);
if (UNEXPECTED(eval_result == NULL)) {
@@ -2055,7 +2051,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
static zend_always_inline zend_string *php_pcre_replace_func(zend_string *regex,
zend_string *subject_str,
- zend_fcall_info *fci, zend_fcall_info_cache *fcc,
+ zend_fcall_info_cache *fcc,
size_t limit, size_t *replace_count, zend_long flags)
{
pcre_cache_entry *pce; /* Compiled regular expression */
@@ -2066,7 +2062,7 @@ static zend_always_inline zend_string *php_pcre_replace_func(zend_string *regex,
return NULL;
}
pce->refcount++;
- result = php_pcre_replace_func_impl(pce, subject_str, fci, fcc, limit, replace_count, flags);
+ result = php_pcre_replace_func_impl(pce, subject_str, fcc, limit, replace_count, flags);
pce->refcount--;
return result;
@@ -2169,13 +2165,13 @@ static zend_always_inline zend_string *php_replace_in_subject(
/* }}} */
static zend_string *php_replace_in_subject_func(zend_string *regex_str, const HashTable *regex_ht,
- zend_fcall_info *fci, zend_fcall_info_cache *fcc,
+ zend_fcall_info_cache *fcc,
zend_string *subject, size_t limit, size_t *replace_count, zend_long flags)
{
zend_string *result;
if (regex_str) {
- result = php_pcre_replace_func(regex_str, subject, fci, fcc, limit, replace_count, flags);
+ result = php_pcre_replace_func(regex_str, subject, fcc, limit, replace_count, flags);
return result;
} else {
/* If regex is an array */
@@ -2196,7 +2192,7 @@ static zend_string *php_replace_in_subject_func(zend_string *regex_str, const Ha
/* Do the actual replacement and put the result back into subject
for further replacements. */
result = php_pcre_replace_func(
- regex_entry_str, subject, fci, fcc, limit, replace_count, flags);
+ regex_entry_str, subject, fcc, limit, replace_count, flags);
zend_tmp_string_release(tmp_regex_entry_str);
zend_string_release(subject);
subject = result;
@@ -2211,7 +2207,7 @@ static zend_string *php_replace_in_subject_func(zend_string *regex_str, const Ha
static size_t php_preg_replace_func_impl(zval *return_value,
zend_string *regex_str, const HashTable *regex_ht,
- zend_fcall_info *fci, zend_fcall_info_cache *fcc,
+ zend_fcall_info_cache *fcc,
zend_string *subject_str, const HashTable *subject_ht, zend_long limit_val, zend_long flags)
{
zend_string *result;
@@ -2219,7 +2215,7 @@ static size_t php_preg_replace_func_impl(zval *return_value,
if (subject_str) {
result = php_replace_in_subject_func(
- regex_str, regex_ht, fci, fcc, subject_str, limit_val, &replace_count, flags);
+ regex_str, regex_ht, fcc, subject_str, limit_val, &replace_count, flags);
if (result != NULL) {
RETVAL_STR(result);
} else {
@@ -2242,7 +2238,7 @@ static size_t php_preg_replace_func_impl(zval *return_value,
}
result = php_replace_in_subject_func(
- regex_str, regex_ht, fci, fcc, subject_entry_str, limit_val, &replace_count, flags);
+ regex_str, regex_ht, fcc, subject_entry_str, limit_val, &replace_count, flags);
if (result != NULL) {
/* Add to return array */
zval zv;
@@ -2405,20 +2401,23 @@ PHP_FUNCTION(preg_replace_callback)
/* Get function parameters and do error-checking. */
ZEND_PARSE_PARAMETERS_START(3, 6)
Z_PARAM_ARRAY_HT_OR_STR(regex_ht, regex_str)
- Z_PARAM_FUNC(fci, fcc)
+ Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc)
Z_PARAM_ARRAY_HT_OR_STR(subject_ht, subject_str)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(limit)
Z_PARAM_ZVAL(zcount)
Z_PARAM_LONG(flags)
- ZEND_PARSE_PARAMETERS_END();
+ ZEND_PARSE_PARAMETERS_END_EX(goto free_trampoline);
replace_count = php_preg_replace_func_impl(return_value, regex_str, regex_ht,
- &fci, &fcc,
+ &fcc,
subject_str, subject_ht, limit, flags);
if (zcount) {
ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count);
}
+
+free_trampoline:
+ zend_release_fcall_info_cache(&fcc);
}
/* }}} */
@@ -2454,10 +2453,6 @@ PHP_FUNCTION(preg_replace_callback_array)
}
zend_fcall_info_cache fcc = empty_fcall_info_cache;
- zend_fcall_info fci = empty_fcall_info;
- fci.size = sizeof(zend_fcall_info);
- /* Copy potential trampoline */
- ZVAL_COPY_VALUE(&fci.function_name, replace);
if (!zend_is_callable(replace, &fcc, NULL)) {
zend_argument_type_error(1, "must contain only valid callbacks");
@@ -2465,9 +2460,10 @@ PHP_FUNCTION(preg_replace_callback_array)
}
zval retval;
- replace_count += php_preg_replace_func_impl(&retval, str_idx_regex, /* regex_ht */ NULL, &fci, &fcc,
+ zend_fcc_addref(&fcc);
+ replace_count += php_preg_replace_func_impl(&retval, str_idx_regex, /* regex_ht */ NULL, &fcc,
subject_str, subject_ht, limit, flags);
- zend_release_fcall_info_cache(&fcc);
+ zend_fcc_dtor(&fcc);
switch (Z_TYPE(retval)) {
case IS_ARRAY:
diff --git a/ext/pcre/tests/preg_replace_callback_trampoline_zpp_freed.phpt b/ext/pcre/tests/preg_replace_callback_trampoline_zpp_freed.phpt
new file mode 100644
index 00000000000..ba249f46b24
--- /dev/null
+++ b/ext/pcre/tests/preg_replace_callback_trampoline_zpp_freed.phpt
@@ -0,0 +1,34 @@
+--TEST--
+preg_replace_callback() with a trampoline and a ZPP failure
+--FILE--
+<?php
+
+class TrampolineTest {
+ public function __call(string $name, array $arguments): string {
+ echo 'Trampoline for ', $name, PHP_EOL;
+ if ($name === 'trampolineThrow') {
+ throw new Exception('boo');
+ }
+ return "'" . $arguments[0][0] . "'";
+ }
+}
+$o = new TrampolineTest();
+$callback = [$o, 'trampoline'];
+$callbackThrow = [$o, 'trampolineThrow'];
+
+$pattern = '@\b\w{1,2}\b@';
+
+$subject = 'a b3 bcd';
+
+$badLimit = 'not an int';
+
+try {
+ $matches = preg_replace_callback($pattern, $callbackThrow, $subject, $badLimit);
+ var_dump($matches);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+TypeError: preg_replace_callback(): Argument #4 ($limit) must be of type int, string given