Skip to content

Commit 7344c50

Browse files
committed
Make PREG_THROW_ON_ERROR only change how errors are delivered.
1 parent 3671745 commit 7344c50

10 files changed

Lines changed: 127 additions & 155 deletions

UPGRADING

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -429,17 +429,20 @@ PHP 8.6 UPGRADE NOTES
429429

430430
- PCRE:
431431
. Added the PREG_THROW_ON_ERROR flag. When passed to a preg_*() function that
432-
accepts a $flags argument, any PCRE error reported by preg_last_error()
433-
throws a \PregException instead of emitting a warning or returning
434-
false/null. The exception's code and message are exactly what
435-
preg_last_error() and preg_last_error_msg() report for the same call: the
436-
flag changes how an error is delivered, not the error itself. This covers
437-
both compilation errors (such as a malformed pattern) and execution errors
438-
(such as an exhausted backtrack limit or malformed UTF-8 input under the /u
439-
modifier). preg_replace() and preg_filter() gained a $flags parameter to
440-
accept it. As on the non-flag path, by-reference outputs (the $matches and
441-
$count arguments) may already have been written when the \PregException is
442-
thrown, so a catch block should not assume they are left untouched.
432+
accepts a $flags argument, a PCRE error additionally throws a \PregException
433+
whose code and message are exactly what preg_last_error() and
434+
preg_last_error_msg() report for that call. The flag does not otherwise
435+
change the call: same warnings, same return value. It covers both execution
436+
errors (such as an exhausted backtrack limit or malformed UTF-8 input under
437+
the /u modifier) and compilation errors (such as a malformed pattern, which
438+
still also emits its usual warning). preg_replace() and preg_filter() gained
439+
a $flags parameter to accept it. A PCRE error raised by a nested preg_*()
440+
call inside a preg_replace_callback()/preg_replace_callback_array() callback
441+
is not attributed to the outer call, so a successful outer call does not
442+
throw on its account. As on the non-flag path, by-reference outputs (the
443+
$matches and $count arguments) may already have been written when the
444+
\PregException is thrown, so a catch block should not assume they are left
445+
untouched.
443446

444447
- PDO_PGSQL:
445448
. Added Pdo\Pgsql::ATTR_CHUNK_SIZE, the number of rows a statement fetches

ext/fileinfo/libmagic/funcs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ file_replace(struct magic_set *ms, const char *pat, const char *rep)
663663

664664
opts |= PCRE2_MULTILINE;
665665
pattern = convert_libmagic_pattern(pat, strlen(pat), opts);
666-
pce = pcre_get_compiled_regex_cache_ex(pattern, 0, 0);
666+
pce = pcre_get_compiled_regex_cache_ex(pattern, 0);
667667
zend_string_release_ex(pattern, 0);
668668
if (pce == NULL) {
669669
rep_cnt = -1;

ext/fileinfo/libmagic/softmagic.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ check_fmt(struct magic_set *ms, const char *fmt)
497497
return 0;
498498

499499
pattern = ZSTR_INIT_LITERAL("~%[-0-9\\.]*s~", 0);
500-
if ((pce = pcre_get_compiled_regex_cache_ex(pattern, 0, 0)) == NULL) {
500+
if ((pce = pcre_get_compiled_regex_cache_ex(pattern, 0)) == NULL) {
501501
rv = -1;
502502
} else {
503503
pcre2_code *re = php_pcre_pce_re(pce);

ext/pcre/php_pcre.c

Lines changed: 45 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -183,40 +183,13 @@ static bool php_pcre_throw_on_error(zend_long flags)
183183
return false;
184184
}
185185

186-
ZEND_ATTRIBUTE_FORMAT(printf, 3, 4)
187-
static void php_pcre_throw_or_warn(zend_long flags, int pcre_errcode, const char *format, ...)
188-
{
189-
va_list args;
190-
char *message;
191-
192-
if (flags & PREG_THROW_ON_ERROR) {
193-
pcre_handle_exec_error(pcre_errcode);
194-
php_pcre_throw_on_error(flags);
195-
return;
196-
}
197-
198-
va_start(args, format);
199-
vspprintf(&message, 0, format, args);
200-
va_end(args);
201-
202-
php_error_docref(NULL, E_WARNING, "%s", message);
203-
efree(message);
204-
205-
pcre_handle_exec_error(pcre_errcode);
206-
}
207-
208186
static void php_pcre_clear_stale_error(zend_long flags)
209187
{
210188
if (flags & PREG_THROW_ON_ERROR) {
211189
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
212190
}
213191
}
214192

215-
static bool php_pcre_flag_error_pending(zend_long flags)
216-
{
217-
return (flags & PREG_THROW_ON_ERROR) && PCRE_G(error_code) != PHP_PCRE_NO_ERROR;
218-
}
219-
220193
static void php_free_pcre_cache(zval *data) /* {{{ */
221194
{
222195
pcre_cache_entry *pce = (pcre_cache_entry *) Z_PTR_P(data);
@@ -625,7 +598,7 @@ static zend_always_inline size_t calculate_unit_length(pcre_cache_entry *pce, co
625598
/* }}} */
626599

627600
/* {{{ pcre_get_compiled_regex_cache */
628-
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bool locale_aware, zend_long flags)
601+
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bool locale_aware)
629602
{
630603
pcre2_code *re = NULL;
631604
#if 10 == PCRE2_MAJOR && 37 == PCRE2_MINOR && !defined(HAVE_BUNDLED_PCRE)
@@ -679,7 +652,8 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
679652
if (key != regex) {
680653
zend_string_release_ex(key, 0);
681654
}
682-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "Empty regular expression");
655+
php_error_docref(NULL, E_WARNING, "Empty regular expression");
656+
pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
683657
return NULL;
684658
}
685659

@@ -690,7 +664,8 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
690664
if (key != regex) {
691665
zend_string_release_ex(key, 0);
692666
}
693-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "Delimiter must not be alphanumeric, backslash, or NUL byte");
667+
php_error_docref(NULL, E_WARNING, "Delimiter must not be alphanumeric, backslash, or NUL byte");
668+
pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
694669
return NULL;
695670
}
696671

@@ -733,10 +708,11 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
733708
zend_string_release_ex(key, 0);
734709
}
735710
if (start_delimiter == end_delimiter) {
736-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "No ending delimiter '%c' found", delimiter);
711+
php_error_docref(NULL,E_WARNING, "No ending delimiter '%c' found", delimiter);
737712
} else {
738-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "No ending matching delimiter '%c' found", delimiter);
713+
php_error_docref(NULL,E_WARNING, "No ending matching delimiter '%c' found", delimiter);
739714
}
715+
pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
740716
return NULL;
741717
}
742718

@@ -787,10 +763,11 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
787763
case 'e': /* legacy eval */
788764
default:
789765
if (pp[-1]) {
790-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "Unknown modifier '%c'", pp[-1]);
766+
php_error_docref(NULL, E_WARNING, "Unknown modifier '%c'", pp[-1]);
791767
} else {
792-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "NUL byte is not a valid modifier");
768+
php_error_docref(NULL, E_WARNING, "NUL byte is not a valid modifier");
793769
}
770+
pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
794771
efree(pattern);
795772
if (key != regex) {
796773
zend_string_release_ex(key, 0);
@@ -808,7 +785,8 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
808785
* set ptr to NULL first so the destructor (pefree) is safe. */
809786
ZVAL_PTR(zv, NULL);
810787
zend_hash_str_del(&char_tables, ZSTR_VAL(BG(ctype_string)), ZSTR_LEN(BG(ctype_string)));
811-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_NOMEMORY, "Failed to generate locale character tables");
788+
php_error_docref(NULL,E_WARNING, "Failed to generate locale character tables");
789+
pcre_handle_exec_error(PCRE2_ERROR_NOMEMORY);
812790
zend_string_release_ex(key, 0);
813791
efree(pattern);
814792
return NULL;
@@ -835,7 +813,8 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
835813
} else {
836814
pcre2_get_error_message(errnumber, error, sizeof(error));
837815
}
838-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "Compilation failed: %s at offset %zu", err_msg, erroffset);
816+
php_error_docref(NULL,E_WARNING, "Compilation failed: %s at offset %zu", err_msg, erroffset);
817+
pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
839818
efree(pattern);
840819
return NULL;
841820
}
@@ -887,7 +866,8 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
887866
zend_string_release_ex(key, 0);
888867
}
889868
pcre2_code_free(new_entry.re);
890-
php_pcre_throw_or_warn(flags, PCRE2_ERROR_INTERNAL, "Internal pcre_pattern_info() error %d", rc);
869+
php_error_docref(NULL, E_WARNING, "Internal pcre_pattern_info() error %d", rc);
870+
pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
891871
return NULL;
892872
}
893873

@@ -920,7 +900,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
920900
/* {{{ pcre_get_compiled_regex_cache */
921901
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
922902
{
923-
return pcre_get_compiled_regex_cache_ex(regex, true, 0);
903+
return pcre_get_compiled_regex_cache_ex(regex, true);
924904
}
925905
/* }}} */
926906

@@ -1159,7 +1139,8 @@ static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, bool global) /* {{{
11591139
php_pcre_clear_stale_error(flags);
11601140

11611141
/* Compile regex or get it from cache. */
1162-
if ((pce = pcre_get_compiled_regex_cache_ex(regex, true, flags)) == NULL) {
1142+
if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
1143+
php_pcre_throw_on_error(flags);
11631144
RETURN_FALSE;
11641145
}
11651146

@@ -1637,7 +1618,7 @@ PHPAPI zend_string *php_pcre_replace(zend_string *regex,
16371618
zend_string *subject_str,
16381619
const char *subject, size_t subject_len,
16391620
zend_string *replace_str,
1640-
size_t limit, size_t *replace_count, zend_long flags)
1621+
size_t limit, size_t *replace_count)
16411622
{
16421623
pcre_cache_entry *pce; /* Compiled regular expression */
16431624
zend_string *result; /* Function result */
@@ -1648,7 +1629,7 @@ PHPAPI zend_string *php_pcre_replace(zend_string *regex,
16481629
}
16491630

16501631
/* Compile regex or get it from cache. */
1651-
if ((pce = pcre_get_compiled_regex_cache_ex(regex, true, flags)) == NULL) {
1632+
if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
16521633
return NULL;
16531634
}
16541635
pce->refcount++;
@@ -2118,7 +2099,7 @@ static zend_always_inline zend_string *php_pcre_replace_func(zend_string *regex,
21182099
zend_string *result; /* Function result */
21192100

21202101
/* Compile regex or get it from cache. */
2121-
if ((pce = pcre_get_compiled_regex_cache_ex(regex, true, flags)) == NULL) {
2102+
if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
21222103
return NULL;
21232104
}
21242105
pce->refcount++;
@@ -2131,7 +2112,7 @@ static zend_always_inline zend_string *php_pcre_replace_func(zend_string *regex,
21312112
/* {{{ php_pcre_replace_array */
21322113
static zend_string *php_pcre_replace_array(HashTable *regex,
21332114
zend_string *replace_str, HashTable *replace_ht,
2134-
zend_string *subject_str, size_t limit, size_t *replace_count, zend_long flags)
2115+
zend_string *subject_str, size_t limit, size_t *replace_count)
21352116
{
21362117
zval *regex_entry;
21372118
zend_string *result;
@@ -2167,7 +2148,7 @@ static zend_string *php_pcre_replace_array(HashTable *regex,
21672148
/* Do the actual replacement and put the result back into subject_str
21682149
for further replacements. */
21692150
result = php_pcre_replace(regex_str, subject_str, ZSTR_VAL(subject_str),
2170-
ZSTR_LEN(subject_str), replace_entry_str, limit, replace_count, flags);
2151+
ZSTR_LEN(subject_str), replace_entry_str, limit, replace_count);
21712152
zend_tmp_string_release(tmp_replace_entry_str);
21722153
zend_tmp_string_release(tmp_regex_str);
21732154
zend_string_release_ex(subject_str, 0);
@@ -2189,7 +2170,7 @@ static zend_string *php_pcre_replace_array(HashTable *regex,
21892170
/* Do the actual replacement and put the result back into subject_str
21902171
for further replacements. */
21912172
result = php_pcre_replace(regex_str, subject_str, ZSTR_VAL(subject_str),
2192-
ZSTR_LEN(subject_str), replace_str, limit, replace_count, flags);
2173+
ZSTR_LEN(subject_str), replace_str, limit, replace_count);
21932174
zend_tmp_string_release(tmp_regex_str);
21942175
zend_string_release_ex(subject_str, 0);
21952176
subject_str = result;
@@ -2208,18 +2189,18 @@ static zend_string *php_pcre_replace_array(HashTable *regex,
22082189
static zend_always_inline zend_string *php_replace_in_subject(
22092190
zend_string *regex_str, HashTable *regex_ht,
22102191
zend_string *replace_str, HashTable *replace_ht,
2211-
zend_string *subject, size_t limit, size_t *replace_count, zend_long flags)
2192+
zend_string *subject, size_t limit, size_t *replace_count)
22122193
{
22132194
zend_string *result;
22142195

22152196
if (regex_str) {
22162197
ZEND_ASSERT(replace_str != NULL);
22172198
result = php_pcre_replace(regex_str, subject, ZSTR_VAL(subject), ZSTR_LEN(subject),
2218-
replace_str, limit, replace_count, flags);
2199+
replace_str, limit, replace_count);
22192200
} else {
22202201
ZEND_ASSERT(regex_ht != NULL);
22212202
result = php_pcre_replace_array(regex_ht, replace_str, replace_ht, subject,
2222-
limit, replace_count, flags);
2203+
limit, replace_count);
22232204
}
22242205
return result;
22252206
}
@@ -2315,10 +2296,6 @@ static size_t php_preg_replace_func_impl(zval *return_value,
23152296
}
23162297
}
23172298
zend_tmp_string_release(tmp_subject_entry_str);
2318-
2319-
if (php_pcre_flag_error_pending(flags)) {
2320-
break;
2321-
}
23222299
} ZEND_HASH_FOREACH_END();
23232300
}
23242301

@@ -2332,7 +2309,6 @@ static void _preg_replace_common(
23322309
HashTable *subject_ht, zend_string *subject_str,
23332310
zend_long limit,
23342311
zval *zcount,
2335-
zend_long flags,
23362312
bool is_filter
23372313
) {
23382314
size_t replace_count = 0;
@@ -2348,7 +2324,7 @@ static void _preg_replace_common(
23482324
if (subject_str) {
23492325
old_replace_count = replace_count;
23502326
result = php_replace_in_subject(regex_str, regex_ht, replace_str, replace_ht,
2351-
subject_str, limit, &replace_count, flags);
2327+
subject_str, limit, &replace_count);
23522328
if (result != NULL) {
23532329
if (!is_filter || replace_count > old_replace_count) {
23542330
RETVAL_STR(result);
@@ -2377,7 +2353,7 @@ static void _preg_replace_common(
23772353
zend_string *tmp_subject_entry_str;
23782354
zend_string *subject_entry_str = zval_get_tmp_string(subject_entry, &tmp_subject_entry_str);
23792355
result = php_replace_in_subject(regex_str, regex_ht, replace_str, replace_ht,
2380-
subject_entry_str, limit, &replace_count, flags);
2356+
subject_entry_str, limit, &replace_count);
23812357

23822358
if (result != NULL) {
23832359
if (!is_filter || replace_count > old_replace_count) {
@@ -2393,10 +2369,6 @@ static void _preg_replace_common(
23932369
}
23942370
}
23952371
zend_tmp_string_release(tmp_subject_entry_str);
2396-
2397-
if (php_pcre_flag_error_pending(flags)) {
2398-
break;
2399-
}
24002372
} ZEND_HASH_FOREACH_END();
24012373
}
24022374

@@ -2432,7 +2404,7 @@ static void preg_replace_common(INTERNAL_FUNCTION_PARAMETERS, bool is_filter)
24322404
regex_ht, regex_str,
24332405
replace_ht, replace_str,
24342406
subject_ht, subject_str,
2435-
limit, zcount, flags, is_filter);
2407+
limit, zcount, is_filter);
24362408

24372409
php_pcre_throw_on_error(flags);
24382410
}
@@ -2460,7 +2432,7 @@ ZEND_FRAMELESS_FUNCTION(preg_replace, 3)
24602432
regex_ht, regex_str,
24612433
replace_ht, replace_str,
24622434
subject_ht, subject_str,
2463-
/* limit */ -1, /* zcount */ NULL, /* flags */ 0, /* is_filter */ false);
2435+
/* limit */ -1, /* zcount */ NULL, /* is_filter */ false);
24642436

24652437
flf_clean:;
24662438
Z_FLF_PARAM_FREE_STR(1, regex_tmp);
@@ -2575,10 +2547,6 @@ PHP_FUNCTION(preg_replace_callback_array)
25752547
if (EG(exception)) {
25762548
goto error;
25772549
}
2578-
2579-
if (php_pcre_throw_on_error(flags)) {
2580-
goto error;
2581-
}
25822550
} ZEND_HASH_FOREACH_END();
25832551

25842552
if (zcount) {
@@ -2591,11 +2559,13 @@ PHP_FUNCTION(preg_replace_callback_array)
25912559
if (GC_FLAGS(subject_ht) & IS_ARRAY_IMMUTABLE) {
25922560
Z_TYPE_FLAGS_P(return_value) = 0;
25932561
}
2594-
return;
25952562
} else {
2596-
RETURN_STR(subject_str);
2563+
RETVAL_STR(subject_str);
25972564
}
25982565

2566+
php_pcre_throw_on_error(flags);
2567+
return;
2568+
25992569
error:
26002570
if (subject_ht) {
26012571
zend_array_release(subject_ht);
@@ -2630,8 +2600,11 @@ PHP_FUNCTION(preg_split)
26302600
Z_PARAM_LONG(flags)
26312601
ZEND_PARSE_PARAMETERS_END();
26322602

2603+
php_pcre_clear_stale_error(flags);
2604+
26332605
/* Compile regex or get it from cache. */
2634-
if ((pce = pcre_get_compiled_regex_cache_ex(regex, true, flags)) == NULL) {
2606+
if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2607+
php_pcre_throw_on_error(flags);
26352608
RETURN_FALSE;
26362609
}
26372610

@@ -2994,8 +2967,11 @@ PHP_FUNCTION(preg_grep)
29942967
Z_PARAM_LONG(flags)
29952968
ZEND_PARSE_PARAMETERS_END();
29962969

2970+
php_pcre_clear_stale_error(flags);
2971+
29972972
/* Compile regex or get it from cache. */
2998-
if ((pce = pcre_get_compiled_regex_cache_ex(regex, true, flags)) == NULL) {
2973+
if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2974+
php_pcre_throw_on_error(flags);
29992975
RETURN_FALSE;
30002976
}
30012977

0 commit comments

Comments
 (0)