Skip to content

Commit aa79a22

Browse files
Nicolas Oelgartnikic
authored andcommitted
Add preg_last_error_msg() function
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.
1 parent bb6e2a1 commit aa79a22

File tree

12 files changed

+160
-12
lines changed

12 files changed

+160
-12
lines changed

UPGRADING

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,11 @@ PHP 8.0 UPGRADE NOTES
435435
6. New Functions
436436
========================================
437437

438+
- PCRE:
439+
. Added preg_last_error_msg(), which returns a human-readable message for
440+
the last PCRE error. It complements preg_last_error(), which returns an
441+
integer enum instead.
442+
438443
- SQLite3:
439444
. Add SQLite3::setAuthorizer() and respective class constants to set a
440445
userland callback that will be used to authorize or not an action on the

ext/pcre/php_pcre.c

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,6 @@ struct _pcre_cache_entry {
5252
uint32_t refcount;
5353
};
5454

55-
enum {
56-
PHP_PCRE_NO_ERROR = 0,
57-
PHP_PCRE_INTERNAL_ERROR,
58-
PHP_PCRE_BACKTRACK_LIMIT_ERROR,
59-
PHP_PCRE_RECURSION_LIMIT_ERROR,
60-
PHP_PCRE_BAD_UTF8_ERROR,
61-
PHP_PCRE_BAD_UTF8_OFFSET_ERROR,
62-
PHP_PCRE_JIT_STACKLIMIT_ERROR
63-
};
64-
65-
6655
PHPAPI ZEND_DECLARE_MODULE_GLOBALS(pcre)
6756

6857
#ifdef HAVE_PCRE_JIT_SUPPORT
@@ -138,6 +127,33 @@ static void pcre_handle_exec_error(int pcre_code) /* {{{ */
138127
}
139128
/* }}} */
140129

130+
static const char *php_pcre_get_error_msg(php_pcre_error_code error_code) /* {{{ */
131+
{
132+
switch (error_code) {
133+
case PHP_PCRE_NO_ERROR:
134+
return "No error";
135+
case PHP_PCRE_INTERNAL_ERROR:
136+
return "Internal error";
137+
case PHP_PCRE_BAD_UTF8_ERROR:
138+
return "Malformed UTF-8 characters, possibly incorrectly encoded";
139+
case PHP_PCRE_BAD_UTF8_OFFSET_ERROR:
140+
return "The offset did not correspond to the beginning of a valid UTF-8 code point";
141+
case PHP_PCRE_BACKTRACK_LIMIT_ERROR:
142+
return "Backtrack limit exhausted";
143+
case PHP_PCRE_RECURSION_LIMIT_ERROR:
144+
return "Recursion limit exhausted";
145+
146+
#ifdef HAVE_PCRE_JIT_SUPPORT
147+
case PHP_PCRE_JIT_STACKLIMIT_ERROR:
148+
return "JIT stack limit exhausted";
149+
#endif
150+
151+
default:
152+
return "Unknown error";
153+
}
154+
}
155+
/* }}} */
156+
141157
static void php_free_pcre_cache(zval *data) /* {{{ */
142158
{
143159
pcre_cache_entry *pce = (pcre_cache_entry *) Z_PTR_P(data);
@@ -2957,6 +2973,16 @@ static PHP_FUNCTION(preg_last_error)
29572973
}
29582974
/* }}} */
29592975

2976+
/* {{{ proto string preg_last_error_msg()
2977+
Returns the error message of the last regexp execution. */
2978+
static PHP_FUNCTION(preg_last_error_msg)
2979+
{
2980+
ZEND_PARSE_PARAMETERS_NONE();
2981+
2982+
RETURN_STRING(php_pcre_get_error_msg(PCRE_G(error_code)));
2983+
}
2984+
/* }}} */
2985+
29602986
/* {{{ module definition structures */
29612987

29622988
static const zend_function_entry pcre_functions[] = {
@@ -2970,6 +2996,7 @@ static const zend_function_entry pcre_functions[] = {
29702996
PHP_FE(preg_quote, arginfo_preg_quote)
29712997
PHP_FE(preg_grep, arginfo_preg_grep)
29722998
PHP_FE(preg_last_error, arginfo_preg_last_error)
2999+
PHP_FE(preg_last_error_msg, arginfo_preg_last_error_msg)
29733000
PHP_FE_END
29743001
};
29753002

ext/pcre/php_pcre.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ extern zend_module_entry pcre_module_entry;
3737

3838
typedef struct _pcre_cache_entry pcre_cache_entry;
3939

40+
typedef enum {
41+
PHP_PCRE_NO_ERROR = 0,
42+
PHP_PCRE_INTERNAL_ERROR,
43+
PHP_PCRE_BACKTRACK_LIMIT_ERROR,
44+
PHP_PCRE_RECURSION_LIMIT_ERROR,
45+
PHP_PCRE_BAD_UTF8_ERROR,
46+
PHP_PCRE_BAD_UTF8_OFFSET_ERROR,
47+
PHP_PCRE_JIT_STACKLIMIT_ERROR
48+
} php_pcre_error_code;
49+
4050
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex);
4151
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, int locale_aware);
4252

@@ -70,7 +80,7 @@ ZEND_BEGIN_MODULE_GLOBALS(pcre)
7080
zend_bool jit;
7181
#endif
7282
zend_bool per_request_cache;
73-
int error_code;
83+
php_pcre_error_code error_code;
7484
/* Used for unmatched subpatterns in OFFSET_CAPTURE mode */
7585
zval unmatched_null_pair;
7686
zval unmatched_empty_pair;

ext/pcre/php_pcre.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ function preg_quote(string $str, ?string $delim_char = null): string {}
3737
function preg_grep(string $regex, array $input, int $flags = 0): array|false {}
3838

3939
function preg_last_error(): int {}
40+
41+
function preg_last_error_msg(): string {}

ext/pcre/php_pcre_arginfo.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ ZEND_END_ARG_INFO()
6363

6464
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_preg_last_error, 0, 0, IS_LONG, 0)
6565
ZEND_END_ARG_INFO()
66+
67+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_preg_last_error_msg, 0, 0, IS_STRING, 0)
68+
ZEND_END_ARG_INFO()

ext/pcre/tests/errors01.phpt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Test preg_split() function : error conditions - Recursion limit exhausted
3+
--INI--
4+
pcre.recursion_limit=1
5+
--FILE--
6+
<?php
7+
8+
var_dump(preg_last_error_msg() === 'No error');
9+
preg_split('/(\d*)/', 'ab2c3u');
10+
var_dump(preg_last_error_msg() === 'Recursion limit exhausted');
11+
12+
?>
13+
--EXPECT--
14+
bool(true)
15+
bool(true)

ext/pcre/tests/errors02.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test preg_split() function : error conditions - Malformed UTF-8
3+
--FILE--
4+
<?php
5+
6+
var_dump(preg_split('/a/u', "a\xff"));
7+
var_dump(preg_last_error_msg() === 'Malformed UTF-8 characters, possibly incorrectly encoded');
8+
9+
?>
10+
--EXPECT--
11+
bool(false)
12+
bool(true)

ext/pcre/tests/errors03.phpt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Test preg_match() function : error conditions - Internal error
3+
--FILE--
4+
<?php
5+
6+
var_dump(preg_match('/', 'Hello world'));
7+
var_dump(preg_last_error_msg() === 'Internal error');
8+
9+
?>
10+
--EXPECTF--
11+
Warning: preg_match(): No ending delimiter '/' found in %s on line %d
12+
bool(false)
13+
bool(true)

ext/pcre/tests/errors04.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Test preg_match_all() function : error conditions - Backtracking limit
3+
--SKIPIF--
4+
<?php
5+
if (@preg_match_all('/\p{N}/', '0123456789', $dummy) === false) {
6+
die("skip no support for \p support PCRE library");
7+
}
8+
?>
9+
--INI--
10+
pcre.backtrack_limit=2
11+
pcre.jit=0
12+
--FILE--
13+
<?php
14+
15+
var_dump(preg_match_all('/.*\p{N}/', '0123456789', $dummy));
16+
var_dump(preg_last_error_msg() === 'Backtrack limit exhausted');
17+
18+
var_dump(preg_match_all('/\p{Nd}/', '0123456789', $dummy));
19+
var_dump(preg_last_error_msg() === 'No error');
20+
21+
?>
22+
--EXPECT--
23+
bool(false)
24+
bool(true)
25+
int(10)
26+
bool(true)

ext/pcre/tests/errors05.phpt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Test preg_match() function : error conditions - jit stacklimit exhausted
3+
--SKIPIF--
4+
<?php
5+
if (ini_get('pcre.jit') === false) {
6+
die("skip no jit built");
7+
}
8+
?>
9+
--INI--
10+
pcre.jit=1
11+
--FILE--
12+
<?php
13+
var_dump(preg_match('/^(foo)+$/', str_repeat('foo', 1024*8192)));
14+
var_dump(preg_last_error_msg() === 'JIT stack limit exhausted');
15+
?>
16+
--EXPECT--
17+
bool(false)
18+
bool(true)

ext/pcre/tests/errors06.phpt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Test preg_match() function : error conditions - Malformed UTF-8 offset
3+
--FILE--
4+
<?php
5+
preg_match('/a/u', "\xE3\x82\xA2", $m, 0, 1);
6+
var_dump(preg_last_error() === PREG_BAD_UTF8_OFFSET_ERROR);
7+
var_dump(preg_last_error_msg() === 'The offset did not correspond to the beginning of a valid UTF-8 code point');
8+
?>
9+
--EXPECT--
10+
bool(true)
11+
bool(true)

sapi/cli/tests/006.phpt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ string(%d) "Extension [ <persistent> extension #%d pcre version %s ] {
165165
}
166166
- Return [ int ]
167167
}
168+
Function [ <internal:pcre> function preg_last_error_msg ] {
169+
170+
- Parameters [0] {
171+
}
172+
- Return [ string ]
173+
}
168174
}
169175
}
170176

0 commit comments

Comments
 (0)