Commit 5bd1bbdc50f for php.net
commit 5bd1bbdc50f9a56056cd8aec97f6ad04e5ffce4e
Author: Louis-Arnaud <la.catoire@gmail.com>
Date: Thu Feb 26 17:14:53 2026 +0100
ext/pcre: Fix preg_grep() returning partial array instead of false on error (#21260)
When a PCRE execution error occurs (e.g. malformed UTF-8 with /u
modifier), preg_grep() was returning a partial result array containing
only the entries processed before the error. All other preg_* functions
return false on execution errors.
After the match loop, check PCRE_G(error_code) and if an error
occurred, destroy the partial array and return false instead.
Fixes GH-11936
diff --git a/UPGRADING b/UPGRADING
index 77d8463afe9..e034d0b0235 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -19,6 +19,11 @@ PHP 8.6 UPGRADE NOTES
1. Backward Incompatible Changes
========================================
+- PCRE:
+ . preg_grep() now returns false instead of a partial array when a PCRE
+ execution error occurs (e.g. malformed UTF-8 input with the /u modifier).
+ This is consistent with other preg_* functions.
+
- Phar:
. Invalid values now throw in Phar::mungServer() instead of being silently
ignored.
diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c
index 4efe0542b76..72a923e650b 100644
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@ -2987,6 +2987,11 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return
if (match_data != mdata) {
pcre2_match_data_free(match_data);
}
+
+ if (PCRE_G(error_code) != PHP_PCRE_NO_ERROR) {
+ zend_array_destroy(Z_ARR_P(return_value));
+ RETURN_FALSE;
+ }
}
/* }}} */
diff --git a/ext/pcre/tests/preg_grep_error_utf8.phpt b/ext/pcre/tests/preg_grep_error_utf8.phpt
new file mode 100644
index 00000000000..efdd7632ab7
--- /dev/null
+++ b/ext/pcre/tests/preg_grep_error_utf8.phpt
@@ -0,0 +1,44 @@
+--TEST--
+preg_grep() returns false on match execution error (e.g. malformed UTF-8)
+--FILE--
+<?php
+// preg_grep should return false when a match execution error occurs,
+// consistent with preg_match behavior. See GH-11936.
+
+// Test 1: preg_match returns false on malformed UTF-8 with /u modifier
+var_dump(preg_match('/.*/u', hex2bin('ff')));
+var_dump(preg_last_error() === PREG_BAD_UTF8_ERROR);
+
+// Test 2: preg_grep should also return false (not an empty/partial array)
+var_dump(preg_grep('/.*/u', [hex2bin('ff')]));
+var_dump(preg_last_error() === PREG_BAD_UTF8_ERROR);
+
+// Test 3: preg_grep with valid entries before the invalid one should
+// return false, not a partial array
+var_dump(preg_grep('/.*/u', ['foo', hex2bin('ff'), 'bar']));
+var_dump(preg_last_error() === PREG_BAD_UTF8_ERROR);
+
+// Test 4: preg_grep with PREG_GREP_INVERT should also return false on error
+var_dump(preg_grep('/.*/u', [hex2bin('ff')], PREG_GREP_INVERT));
+var_dump(preg_last_error() === PREG_BAD_UTF8_ERROR);
+
+// Test 5: preg_grep without error still returns an array
+var_dump(preg_grep('/.*/u', ['foo', 'bar']));
+var_dump(preg_last_error() === PREG_NO_ERROR);
+?>
+--EXPECTF--
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+array(2) {
+ [0]=>
+ string(3) "foo"
+ [1]=>
+ string(3) "bar"
+}
+bool(true)