Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 49 additions & 60 deletions ext/pcre/php_pcre.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ ZEND_TLS pcre2_compile_context *cctx = NULL;
ZEND_TLS pcre2_match_context *mctx = NULL;
ZEND_TLS pcre2_match_data *mdata = NULL;
ZEND_TLS bool mdata_used = 0;
ZEND_TLS uint8_t pcre2_init_ok = 0;
ZEND_TLS bool pcre2_init_ok = false;
#if defined(ZTS) && defined(HAVE_PCRE_JIT_SUPPORT)
static MUTEX_T pcre_mt = NULL;
#define php_pcre_mutex_alloc() \
Expand Down Expand Up @@ -104,7 +104,7 @@ static void php_pcre_free_char_table(zval *data)

static void pcre_handle_exec_error(int pcre_code) /* {{{ */
{
int preg_code = 0;
php_pcre_error_code preg_code = PHP_PCRE_NO_ERROR;

switch (pcre_code) {
case PCRE2_ERROR_MATCHLIMIT:
Expand Down Expand Up @@ -204,23 +204,23 @@ static void php_pcre_init_pcre2(uint8_t jit)
if (!gctx) {
gctx = pcre2_general_context_create(php_pcre_malloc, php_pcre_free, NULL);
if (!gctx) {
pcre2_init_ok = 0;
pcre2_init_ok = false;
return;
}
}

if (!cctx) {
cctx = pcre2_compile_context_create(gctx);
if (!cctx) {
pcre2_init_ok = 0;
pcre2_init_ok = false;
return;
}
}

if (!mctx) {
mctx = pcre2_match_context_create(gctx);
if (!mctx) {
pcre2_init_ok = 0;
pcre2_init_ok = false;
return;
}
}
Expand All @@ -229,7 +229,7 @@ static void php_pcre_init_pcre2(uint8_t jit)
if (jit && !jit_stack) {
jit_stack = pcre2_jit_stack_create(PCRE_JIT_STACK_MIN_SIZE, PCRE_JIT_STACK_MAX_SIZE, gctx);
if (!jit_stack) {
pcre2_init_ok = 0;
pcre2_init_ok = false;
return;
}
}
Expand All @@ -238,12 +238,12 @@ static void php_pcre_init_pcre2(uint8_t jit)
if (!mdata) {
mdata = pcre2_match_data_create(PHP_PCRE_PREALLOC_MDATA_SIZE, gctx);
if (!mdata) {
pcre2_init_ok = 0;
pcre2_init_ok = false;
return;
}
}

pcre2_init_ok = 1;
pcre2_init_ok = true;
}/*}}}*/

static void php_pcre_shutdown_pcre2(void)
Expand Down Expand Up @@ -277,7 +277,7 @@ static void php_pcre_shutdown_pcre2(void)
mdata = NULL;
}

pcre2_init_ok = 0;
pcre2_init_ok = false;
}/*}}}*/

static PHP_GINIT_FUNCTION(pcre) /* {{{ */
Expand Down Expand Up @@ -517,7 +517,7 @@ static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats)
}

/* {{{ static make_subpats_table */
static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce)
static zend_string **make_subpats_table(uint32_t name_cnt, const pcre_cache_entry *pce)
{
uint32_t num_subpats = pce->capture_count + 1;
uint32_t name_size, ni = 0;
Expand Down Expand Up @@ -553,7 +553,7 @@ static zend_string **ensure_subpats_table(uint32_t name_cnt, pcre_cache_entry *p

/* {{{ static calculate_unit_length */
/* Calculates the byte length of the next character. Assumes valid UTF-8 for PCRE2_UTF. */
static zend_always_inline size_t calculate_unit_length(pcre_cache_entry *pce, const char *start)
static zend_always_inline size_t calculate_unit_length(const pcre_cache_entry *pce, const char *start)
{
size_t unit_len;

Expand Down Expand Up @@ -750,14 +750,14 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
}

if (key != regex) {
zv = zend_hash_str_lookup(&char_tables, ZSTR_VAL(BG(ctype_string)), ZSTR_LEN(BG(ctype_string)));
zv = zend_hash_lookup(&char_tables, BG(ctype_string));

@devnexen devnexen Sep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the only change that concerns me a bit. would it be possible to try at least locally this change (with ASAN) ?

❯ can you draft a test to trigger this issue ?zm_globals_dtor_pcre → zend_hash_destroy(&char_tables)).

--TEST--
preg_match(): locale-keyed char tables cache must not borrow a request-lifetime key
--SKIPIF--
<?php
if (setlocale(LC_CTYPE, 'C.UTF-8', 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8') === false) {
    die('skip no suitable non-C locale available');
}
?>
--FILE--
<?php
var_dump(setlocale(LC_CTYPE, 'C.UTF-8', 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8') !== false);
setlocale(LC_CTYPE, setlocale(LC_CTYPE, 0));
var_dump(preg_match('/[[:alpha:]]/', 'a'));
?>
--EXPECT--
bool(true)
int(1)

if (Z_ISNULL_P(zv)) {
tables = pcre2_maketables(gctx);
if (UNEXPECTED(!tables)) {
/* Remove the placeholder entry created by zend_hash_str_lookup(),
/* Remove the placeholder entry created by zend_hash_lookup(),
* set ptr to NULL first so the destructor (pefree) is safe. */
ZVAL_PTR(zv, NULL);
zend_hash_str_del(&char_tables, ZSTR_VAL(BG(ctype_string)), ZSTR_LEN(BG(ctype_string)));
zend_hash_del(&char_tables, BG(ctype_string));
php_error_docref(NULL,E_WARNING, "Failed to generate locale character tables");
pcre_handle_exec_error(PCRE2_ERROR_NOMEMORY);
zend_string_release_ex(key, 0);
Expand Down Expand Up @@ -1127,7 +1127,7 @@ static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, bool global) /* {{{
/* }}} */

static zend_always_inline bool is_known_valid_utf8(
zend_string *subject_str, PCRE2_SIZE start_offset) {
const zend_string *subject_str, PCRE2_SIZE start_offset) {
if (!ZSTR_IS_VALID_UTF8(subject_str)) {
/* We don't know whether the string is valid UTF-8 or not. */
return false;
Expand Down Expand Up @@ -1515,53 +1515,53 @@ PHP_FUNCTION(preg_match_all)
}
/* }}} */

/* {{{ preg_get_backref */
static int preg_get_backref(char **str, int *backref)
static bool preg_get_backref(char **str, int *backref)
{
char in_brace = 0;
bool in_brace = false;
char *walk = *str;

if (walk[1] == 0)
return 0;
if (walk[1] == 0) {
return false;
}

if (*walk == '$' && walk[1] == '{') {
in_brace = 1;
in_brace = true;
walk++;
}
walk++;

if (*walk >= '0' && *walk <= '9') {
*backref = *walk - '0';
walk++;
} else
return 0;
} else {
return false;
}

if (*walk && *walk >= '0' && *walk <= '9') {
*backref = *backref * 10 + *walk - '0';
walk++;
}

if (in_brace) {
if (*walk != '}')
return 0;
else
walk++;
if (*walk != '}') {
return false;
}
walk++;
}

*str = walk;
return 1;
return true;
}
/* }}} */

/* Return NULL if an exception has occurred */
static zend_string *preg_do_repl_func(zend_fcall_info *fci, zend_fcall_info_cache *fcc, const char *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 *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)
{
zend_string *result_str = NULL;
zval retval; /* Function return value */
zval arg; /* Argument to pass to function */

array_init_size(&arg, count + (mark ? 1 : 0));
populate_subpat_array(Z_ARRVAL(arg), subject, offsets, subpat_names, num_subpats, count, mark, flags);
populate_subpat_array(Z_ARRVAL(arg), ZSTR_VAL(subject), offsets, subpat_names, num_subpats, count, mark, flags);

fci->retval = &retval;
fci->param_count = 1;
Expand Down Expand Up @@ -1953,7 +1953,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, ZSTR_VAL(subject_str), offsets, subpat_names, num_subpats, count,
fci, fcc, subject_str, offsets, subpat_names, num_subpats, count,
pcre2_get_mark(match_data), flags);

if (UNEXPECTED(eval_result == NULL)) {
Expand Down Expand Up @@ -2074,8 +2074,8 @@ static zend_always_inline zend_string *php_pcre_replace_func(zend_string *regex,
}

/* {{{ php_pcre_replace_array */
static zend_string *php_pcre_replace_array(HashTable *regex,
zend_string *replace_str, HashTable *replace_ht,
static zend_string *php_pcre_replace_array(const HashTable *regex,
zend_string *replace_str, const HashTable *replace_ht,
zend_string *subject_str, size_t limit, size_t *replace_count)
{
zval *regex_entry;
Expand All @@ -2092,7 +2092,6 @@ static zend_string *php_pcre_replace_array(HashTable *regex,
zend_string *tmp_regex_str;
zend_string *regex_str = zval_get_tmp_string(regex_entry, &tmp_regex_str);
zend_string *replace_entry_str, *tmp_replace_entry_str;
zval *zv;

/* Get current entry */
while (1) {
Expand All @@ -2101,7 +2100,7 @@ static zend_string *php_pcre_replace_array(HashTable *regex,
tmp_replace_entry_str = NULL;
break;
}
zv = ZEND_HASH_ELEMENT(replace_ht, replace_idx);
const zval *zv = ZEND_HASH_ELEMENT(replace_ht, replace_idx);
replace_idx++;
if (Z_TYPE_P(zv) != IS_UNDEF) {
replace_entry_str = zval_get_tmp_string(zv, &tmp_replace_entry_str);
Expand Down Expand Up @@ -2151,8 +2150,8 @@ static zend_string *php_pcre_replace_array(HashTable *regex,

/* {{{ php_replace_in_subject */
static zend_always_inline zend_string *php_replace_in_subject(
zend_string *regex_str, HashTable *regex_ht,
zend_string *replace_str, HashTable *replace_ht,
zend_string *regex_str, const HashTable *regex_ht,
zend_string *replace_str, const HashTable *replace_ht,
zend_string *subject, size_t limit, size_t *replace_count)
{
zend_string *result;
Expand Down Expand Up @@ -2181,14 +2180,13 @@ static zend_string *php_replace_in_subject_func(zend_string *regex_str, const Ha
return result;
} else {
/* If regex is an array */
zval *regex_entry;

ZEND_ASSERT(regex_ht != NULL);

zend_string_addref(subject);

/* For each entry in the regex array, get the entry */
ZEND_HASH_FOREACH_VAL(regex_ht, regex_entry) {
ZEND_HASH_FOREACH_VAL(regex_ht, zval *regex_entry) {
/* Make sure we're dealing with strings. */
zend_string *tmp_regex_entry_str;
zend_string *regex_entry_str = zval_try_get_tmp_string(regex_entry, &tmp_regex_entry_str);
Expand Down Expand Up @@ -2230,18 +2228,14 @@ static size_t php_preg_replace_func_impl(zval *return_value,
}
} else {
/* if subject is an array */
zval *subject_entry, zv;
zend_string *string_key;
zend_ulong num_key;

ZEND_ASSERT(subject_ht != NULL);

array_init_size(return_value, zend_hash_num_elements(subject_ht));
HashTable *return_value_ht = Z_ARRVAL_P(return_value);

/* For each subject entry, convert it to string, then perform replacement
and add the result to the return_value array. */
ZEND_HASH_FOREACH_KEY_VAL(subject_ht, num_key, string_key, subject_entry) {
ZEND_HASH_FOREACH_KEY_VAL(subject_ht, zend_ulong num_key, zend_string *string_key, zval *subject_entry) {
zend_string *tmp_subject_entry_str;
zend_string *subject_entry_str = zval_try_get_tmp_string(subject_entry, &tmp_subject_entry_str);
if (UNEXPECTED(subject_entry_str == NULL)) {
Expand All @@ -2252,6 +2246,7 @@ static size_t php_preg_replace_func_impl(zval *return_value,
regex_str, regex_ht, fci, fcc, subject_entry_str, limit_val, &replace_count, flags);
if (result != NULL) {
/* Add to return array */
zval zv;
ZVAL_STR(&zv, result);
if (string_key) {
zend_hash_add_new(return_value_ht, string_key, &zv);
Expand All @@ -2269,8 +2264,8 @@ static size_t php_preg_replace_func_impl(zval *return_value,
static void _preg_replace_common(
zval *return_value,
HashTable *regex_ht, zend_string *regex_str,
HashTable *replace_ht, zend_string *replace_str,
HashTable *subject_ht, zend_string *subject_str,
const HashTable *replace_ht, zend_string *replace_str,
const HashTable *subject_ht, zend_string *subject_str,
zend_long limit,
zval *zcount,
bool is_filter
Expand Down Expand Up @@ -2301,18 +2296,14 @@ static void _preg_replace_common(
}
} else {
/* if subject is an array */
zval *subject_entry, zv;
zend_string *string_key;
zend_ulong num_key;

ZEND_ASSERT(subject_ht != NULL);

array_init_size(return_value, zend_hash_num_elements(subject_ht));
HashTable *return_value_ht = Z_ARRVAL_P(return_value);

/* For each subject entry, convert it to string, then perform replacement
and add the result to the return_value array. */
ZEND_HASH_FOREACH_KEY_VAL(subject_ht, num_key, string_key, subject_entry) {
ZEND_HASH_FOREACH_KEY_VAL(subject_ht, zend_ulong num_key, zend_string *string_key, zval *subject_entry) {
old_replace_count = replace_count;
zend_string *tmp_subject_entry_str;
zend_string *subject_entry_str = zval_get_tmp_string(subject_entry, &tmp_subject_entry_str);
Expand All @@ -2322,6 +2313,7 @@ static void _preg_replace_common(
if (result != NULL) {
if (!is_filter || replace_count > old_replace_count) {
/* Add to return array */
zval zv;
ZVAL_STR(&zv, result);
if (string_key) {
zend_hash_add_new(return_value_ht, string_key, &zv);
Expand Down Expand Up @@ -2434,9 +2426,9 @@ PHP_FUNCTION(preg_replace_callback)
/* {{{ Perform Perl-style regular expression replacement using replacement callback. */
PHP_FUNCTION(preg_replace_callback_array)
{
zval *replace, *zcount = NULL;
zval *zcount = NULL;
HashTable *pattern, *subject_ht;
zend_string *subject_str, *str_idx_regex;
zend_string *subject_str;
zend_long limit = -1, flags = 0;
size_t replace_count = 0;

Expand All @@ -2456,7 +2448,7 @@ PHP_FUNCTION(preg_replace_callback_array)
GC_TRY_ADDREF(subject_str);
}

ZEND_HASH_FOREACH_STR_KEY_VAL(pattern, str_idx_regex, replace) {
ZEND_HASH_FOREACH_STR_KEY_VAL(pattern, zend_string *str_idx_regex, zval *replace) {
if (!str_idx_regex) {
zend_argument_type_error(1, "must contain only string patterns as keys");
goto error;
Expand Down Expand Up @@ -2561,7 +2553,7 @@ PHP_FUNCTION(preg_split)
/* }}} */

/* {{{ php_pcre_split */
PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
PHPAPI void php_pcre_split_impl(const pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
zend_long limit_val, zend_long flags)
{
uint32_t options; /* Execution options */
Expand Down Expand Up @@ -2924,12 +2916,9 @@ PHP_FUNCTION(preg_grep)

PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return_value, zend_long flags) /* {{{ */
{
zval *entry; /* An entry in the input array */
uint32_t num_subpats; /* Number of captured subpatterns */
int count; /* Count of matched subpatterns */
uint32_t options; /* Execution options */
zend_string *string_key;
zend_ulong num_key;
bool invert; /* Whether to return non-matching
entries */
bool old_mdata_used;
Expand Down Expand Up @@ -2960,7 +2949,7 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return
options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;

/* Go through the input array */
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(input), num_key, string_key, entry) {
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(input), zend_ulong num_key, zend_string *string_key, zval *entry) {
zend_string *tmp_subject_str;
zend_string *subject_str = zval_get_tmp_string(entry, &tmp_subject_str);

Expand Down
2 changes: 1 addition & 1 deletion ext/pcre/php_pcre.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str
PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *subject_str, const char *subject, size_t subject_len, zend_string *replace_str,
size_t limit, size_t *replace_count);

PHPAPI void php_pcre_split_impl( pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
PHPAPI void php_pcre_split_impl(const pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
zend_long limit_val, zend_long flags);

PHPAPI void php_pcre_grep_impl( pcre_cache_entry *pce, zval *input, zval *return_value,
Expand Down
Loading