]> granicus.if.org Git - php/commitdiff
Add preg_last_error_msg() function
authorNicolas Oelgart <nicolas.oelgart@atrapalo.com>
Mon, 17 Feb 2020 13:29:12 +0000 (14:29 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Tue, 25 Feb 2020 09:26:03 +0000 (10:26 +0100)
Provides the last PCRE error as a human-readable message, similar
to functionality existing in other extensions, such as
json_last_error_msg().

Closes GH-5185.

12 files changed:
UPGRADING
ext/pcre/php_pcre.c
ext/pcre/php_pcre.h
ext/pcre/php_pcre.stub.php
ext/pcre/php_pcre_arginfo.h
ext/pcre/tests/errors01.phpt [new file with mode: 0644]
ext/pcre/tests/errors02.phpt [new file with mode: 0644]
ext/pcre/tests/errors03.phpt [new file with mode: 0644]
ext/pcre/tests/errors04.phpt [new file with mode: 0644]
ext/pcre/tests/errors05.phpt [new file with mode: 0644]
ext/pcre/tests/errors06.phpt [new file with mode: 0644]
sapi/cli/tests/006.phpt

index 2696cf83767812c0c0d018a7026d4c39ec9ce95f..133747d28f58aa30df380f0e77d043ddeb77ffaa 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -435,6 +435,11 @@ PHP 8.0 UPGRADE NOTES
 6. New Functions
 ========================================
 
+- PCRE:
+  . Added preg_last_error_msg(), which returns a human-readable message for
+    the last PCRE error. It complements preg_last_error(), which returns an
+    integer enum instead.
+
 - SQLite3:
   . Add SQLite3::setAuthorizer() and respective class constants to set a
     userland callback that will be used to authorize or not an action on the
index edf48ca1f465139238dddf58cfa0a498df5edeec..fdc1b1b8563ad91bd7bb50b928dfe80224923a57 100644 (file)
@@ -52,17 +52,6 @@ struct _pcre_cache_entry {
        uint32_t refcount;
 };
 
-enum {
-       PHP_PCRE_NO_ERROR = 0,
-       PHP_PCRE_INTERNAL_ERROR,
-       PHP_PCRE_BACKTRACK_LIMIT_ERROR,
-       PHP_PCRE_RECURSION_LIMIT_ERROR,
-       PHP_PCRE_BAD_UTF8_ERROR,
-       PHP_PCRE_BAD_UTF8_OFFSET_ERROR,
-       PHP_PCRE_JIT_STACKLIMIT_ERROR
-};
-
-
 PHPAPI ZEND_DECLARE_MODULE_GLOBALS(pcre)
 
 #ifdef HAVE_PCRE_JIT_SUPPORT
@@ -138,6 +127,33 @@ static void pcre_handle_exec_error(int pcre_code) /* {{{ */
 }
 /* }}} */
 
+static const char *php_pcre_get_error_msg(php_pcre_error_code error_code) /* {{{ */
+{
+    switch (error_code) {
+        case PHP_PCRE_NO_ERROR:
+            return "No error";
+        case PHP_PCRE_INTERNAL_ERROR:
+            return "Internal error";
+        case PHP_PCRE_BAD_UTF8_ERROR:
+            return "Malformed UTF-8 characters, possibly incorrectly encoded";
+        case PHP_PCRE_BAD_UTF8_OFFSET_ERROR:
+            return "The offset did not correspond to the beginning of a valid UTF-8 code point";
+        case PHP_PCRE_BACKTRACK_LIMIT_ERROR:
+            return "Backtrack limit exhausted";
+        case PHP_PCRE_RECURSION_LIMIT_ERROR:
+            return "Recursion limit exhausted";
+
+#ifdef HAVE_PCRE_JIT_SUPPORT
+        case PHP_PCRE_JIT_STACKLIMIT_ERROR:
+            return "JIT stack limit exhausted";
+#endif
+
+        default:
+            return "Unknown error";
+    }
+}
+/* }}} */
+
 static void php_free_pcre_cache(zval *data) /* {{{ */
 {
        pcre_cache_entry *pce = (pcre_cache_entry *) Z_PTR_P(data);
@@ -2957,6 +2973,16 @@ static PHP_FUNCTION(preg_last_error)
 }
 /* }}} */
 
+/* {{{ proto string preg_last_error_msg()
+   Returns the error message of the last regexp execution. */
+static PHP_FUNCTION(preg_last_error_msg)
+{
+    ZEND_PARSE_PARAMETERS_NONE();
+
+    RETURN_STRING(php_pcre_get_error_msg(PCRE_G(error_code)));
+}
+/* }}} */
+
 /* {{{ module definition structures */
 
 static const zend_function_entry pcre_functions[] = {
@@ -2970,6 +2996,7 @@ static const zend_function_entry pcre_functions[] = {
        PHP_FE(preg_quote,                                      arginfo_preg_quote)
        PHP_FE(preg_grep,                                       arginfo_preg_grep)
        PHP_FE(preg_last_error,                         arginfo_preg_last_error)
+       PHP_FE(preg_last_error_msg,                     arginfo_preg_last_error_msg)
        PHP_FE_END
 };
 
index 3c2fff42518653fdff4310eac7d06fba755584c6..bf78f992fa85bf1bde62482ebf68be15ae0ead0f 100644 (file)
@@ -37,6 +37,16 @@ extern zend_module_entry pcre_module_entry;
 
 typedef struct _pcre_cache_entry pcre_cache_entry;
 
+typedef enum {
+    PHP_PCRE_NO_ERROR = 0,
+    PHP_PCRE_INTERNAL_ERROR,
+    PHP_PCRE_BACKTRACK_LIMIT_ERROR,
+    PHP_PCRE_RECURSION_LIMIT_ERROR,
+    PHP_PCRE_BAD_UTF8_ERROR,
+    PHP_PCRE_BAD_UTF8_OFFSET_ERROR,
+    PHP_PCRE_JIT_STACKLIMIT_ERROR
+} php_pcre_error_code;
+
 PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex);
 PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, int locale_aware);
 
@@ -70,7 +80,7 @@ ZEND_BEGIN_MODULE_GLOBALS(pcre)
        zend_bool jit;
 #endif
        zend_bool per_request_cache;
-       int  error_code;
+       php_pcre_error_code error_code;
        /* Used for unmatched subpatterns in OFFSET_CAPTURE mode */
        zval unmatched_null_pair;
        zval unmatched_empty_pair;
index 397ab22159aa49735583dc2aaceddfa718b37fa2..ccedc7207d8c6a678d3472b94e2ca8bf9f54024d 100644 (file)
@@ -37,3 +37,5 @@ function preg_quote(string $str, ?string $delim_char = null): string {}
 function preg_grep(string $regex, array $input, int $flags = 0): array|false {}
 
 function preg_last_error(): int {}
+
+function preg_last_error_msg(): string {}
index 4c7308baabcc7b9fcd04a47b53fd219f41a0ec6d..41591da52f1e4b4355d67a9ba4714ec109ef3671 100644 (file)
@@ -63,3 +63,6 @@ ZEND_END_ARG_INFO()
 
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_preg_last_error, 0, 0, IS_LONG, 0)
 ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_preg_last_error_msg, 0, 0, IS_STRING, 0)
+ZEND_END_ARG_INFO()
diff --git a/ext/pcre/tests/errors01.phpt b/ext/pcre/tests/errors01.phpt
new file mode 100644 (file)
index 0000000..c239309
--- /dev/null
@@ -0,0 +1,15 @@
+--TEST--
+Test preg_split() function : error conditions - Recursion limit exhausted
+--INI--
+pcre.recursion_limit=1
+--FILE--
+<?php
+
+var_dump(preg_last_error_msg() === 'No error');
+preg_split('/(\d*)/', 'ab2c3u');
+var_dump(preg_last_error_msg() === 'Recursion limit exhausted');
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
diff --git a/ext/pcre/tests/errors02.phpt b/ext/pcre/tests/errors02.phpt
new file mode 100644 (file)
index 0000000..d350bae
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Test preg_split() function : error conditions - Malformed UTF-8
+--FILE--
+<?php
+
+var_dump(preg_split('/a/u', "a\xff"));
+var_dump(preg_last_error_msg() === 'Malformed UTF-8 characters, possibly incorrectly encoded');
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
diff --git a/ext/pcre/tests/errors03.phpt b/ext/pcre/tests/errors03.phpt
new file mode 100644 (file)
index 0000000..1f519cb
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Test preg_match() function : error conditions - Internal error
+--FILE--
+<?php
+
+var_dump(preg_match('/', 'Hello world'));
+var_dump(preg_last_error_msg() === 'Internal error');
+
+?>
+--EXPECTF--
+Warning: preg_match(): No ending delimiter '/' found in %s on line %d
+bool(false)
+bool(true)
diff --git a/ext/pcre/tests/errors04.phpt b/ext/pcre/tests/errors04.phpt
new file mode 100644 (file)
index 0000000..340b084
--- /dev/null
@@ -0,0 +1,26 @@
+--TEST--
+Test preg_match_all() function : error conditions - Backtracking limit
+--SKIPIF--
+<?php
+if (@preg_match_all('/\p{N}/', '0123456789', $dummy) === false) {
+       die("skip no support for \p support PCRE library");
+}
+?>
+--INI--
+pcre.backtrack_limit=2
+pcre.jit=0
+--FILE--
+<?php
+
+var_dump(preg_match_all('/.*\p{N}/', '0123456789', $dummy));
+var_dump(preg_last_error_msg() === 'Backtrack limit exhausted');
+
+var_dump(preg_match_all('/\p{Nd}/', '0123456789', $dummy));
+var_dump(preg_last_error_msg() === 'No error');
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
+int(10)
+bool(true)
diff --git a/ext/pcre/tests/errors05.phpt b/ext/pcre/tests/errors05.phpt
new file mode 100644 (file)
index 0000000..13fabc4
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+Test preg_match() function : error conditions - jit stacklimit exhausted
+--SKIPIF--
+<?php
+if (ini_get('pcre.jit') === false) {
+       die("skip no jit built");
+}
+?>
+--INI--
+pcre.jit=1
+--FILE--
+<?php
+var_dump(preg_match('/^(foo)+$/', str_repeat('foo', 1024*8192)));
+var_dump(preg_last_error_msg() === 'JIT stack limit exhausted');
+?>
+--EXPECT--
+bool(false)
+bool(true)
diff --git a/ext/pcre/tests/errors06.phpt b/ext/pcre/tests/errors06.phpt
new file mode 100644 (file)
index 0000000..8e78244
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Test preg_match() function : error conditions - Malformed UTF-8 offset
+--FILE--
+<?php
+preg_match('/a/u', "\xE3\x82\xA2", $m, 0, 1);
+var_dump(preg_last_error() === PREG_BAD_UTF8_OFFSET_ERROR);
+var_dump(preg_last_error_msg() === 'The offset did not correspond to the beginning of a valid UTF-8 code point');
+?>
+--EXPECT--
+bool(true)
+bool(true)
index b1f756d17f73d35d7afd7da8fff4995e0703c733..f1e3a0a33de1a7a31c90e7f522d8866a68672bff 100644 (file)
@@ -165,6 +165,12 @@ string(%d) "Extension [ <persistent> extension #%d pcre version %s ] {
       }
       - Return [ int ]
     }
+    Function [ <internal:pcre> function preg_last_error_msg ] {
+
+      - Parameters [0] {
+      }
+      - Return [ string ]
+    }
   }
 }