diff --git a/NEWS b/NEWS
index aa6f7f81a89d..95a0ab97ee1f 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,10 @@ PHP NEWS
. Fixed bug GH-20726 (Crash with ODBC connection pooling when the DSN
carries no credentials). (iliaal)
+- PHPDBG:
+ . Fixed fleaked lowercased lookup keys in phpdbg_resolve_opline_break.
+ (jorgsowa)
+
- Session:
. Fixed bug GH-21314 (Different session garbage collector behavior between
PHP 8.3 and PHP 8.5). (jorgsowa)
diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c
index d10b4d83fc3e..0173d2ef47a2 100644
--- a/Zend/Optimizer/zend_optimizer.c
+++ b/Zend/Optimizer/zend_optimizer.c
@@ -104,9 +104,7 @@ zend_result zend_optimizer_eval_special_func_call(
zval *result, const zend_string *name, zend_string *arg) {
if (zend_string_equals_literal(name, "function_exists") ||
zend_string_equals_literal(name, "is_callable")) {
- zend_string *lc_name = zend_string_tolower(arg);
- const zend_internal_function *func = zend_hash_find_ptr(EG(function_table), lc_name);
- zend_string_release_ex(lc_name, 0);
+ const zend_internal_function *func = zend_hash_find_ptr_lc(EG(function_table), arg);
if (func && func->type == ZEND_INTERNAL_FUNCTION
&& func->module->type == MODULE_PERSISTENT
@@ -120,9 +118,7 @@ zend_result zend_optimizer_eval_special_func_call(
return FAILURE;
}
if (zend_string_equals_literal(name, "extension_loaded")) {
- zend_string *lc_name = zend_string_tolower(arg);
- zend_module_entry *m = zend_hash_find_ptr(&module_registry, lc_name);
- zend_string_release_ex(lc_name, 0);
+ zend_module_entry *m = zend_hash_find_ptr_lc(&module_registry, arg);
if (!m) {
if (PG(enable_dl)) {
diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c
index 2dceac2512db..87acf073a2da 100644
--- a/Zend/zend_builtin_functions.c
+++ b/Zend/zend_builtin_functions.c
@@ -953,7 +953,6 @@ ZEND_FUNCTION(method_exists)
{
zval *klass;
zend_string *method_name;
- zend_string *lcname;
zend_class_entry *ce;
zend_function *func;
@@ -974,9 +973,7 @@ ZEND_FUNCTION(method_exists)
RETURN_THROWS();
}
- lcname = zend_string_tolower(method_name);
- func = zend_hash_find_ptr(&ce->function_table, lcname);
- zend_string_release_ex(lcname, 0);
+ func = zend_hash_find_ptr_lc(&ce->function_table, method_name);
if (func) {
/* Exclude shadow properties when checking a method on a specific class. Include
@@ -2219,7 +2216,6 @@ ZEND_FUNCTION(extension_loaded)
ZEND_FUNCTION(get_extension_funcs)
{
zend_string *extension_name;
- zend_string *lcname;
bool array;
zend_module_entry *module;
zend_function *zif;
@@ -2228,9 +2224,7 @@ ZEND_FUNCTION(get_extension_funcs)
RETURN_THROWS();
}
if (strncasecmp(ZSTR_VAL(extension_name), "zend", sizeof("zend"))) {
- lcname = zend_string_tolower(extension_name);
- module = zend_hash_find_ptr(&module_registry, lcname);
- zend_string_release_ex(lcname, 0);
+ module = zend_hash_find_ptr_lc(&module_registry, extension_name);
} else {
module = zend_hash_str_find_ptr(&module_registry, "core", sizeof("core") - 1);
}
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index 8df6a5599d30..848b9d209b2d 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -264,11 +264,7 @@ static zend_class_entry *lookup_class_ex(
bool in_preload = CG(compiler_options) & ZEND_COMPILE_PRELOAD;
if (UNEXPECTED(!EG(active) && !in_preload)) {
- zend_string *lc_name = zend_string_tolower(name);
-
- ce = zend_hash_find_ptr(CG(class_table), lc_name);
-
- zend_string_release(lc_name);
+ ce = zend_hash_find_ptr_lc(CG(class_table), name);
if (register_unresolved && !ce) {
zend_error_noreturn(
@@ -2530,7 +2526,6 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
size_t i, j = 0;
zend_trait_precedence *cur_precedence;
zend_trait_method_reference *cur_method_ref;
- zend_string *lc_trait_name;
zend_string *lcname;
HashTable **exclude_tables = NULL;
zend_class_entry **aliases = NULL;
@@ -2545,9 +2540,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
while ((cur_precedence = precedences[i])) {
/** Resolve classes for all precedence operations. */
cur_method_ref = &cur_precedence->trait_method;
- lc_trait_name = zend_string_tolower(cur_method_ref->class_name);
- trait = zend_hash_find_ptr(EG(class_table), lc_trait_name);
- zend_string_release_ex(lc_trait_name, 0);
+ trait = zend_hash_find_ptr_lc(EG(class_table), cur_method_ref->class_name);
if (!trait || !(trait->ce_flags & ZEND_ACC_LINKED)) {
zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name));
}
@@ -2574,9 +2567,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
zend_class_entry *exclude_ce;
uint32_t trait_num;
- lc_trait_name = zend_string_tolower(class_name);
- exclude_ce = zend_hash_find_ptr(EG(class_table), lc_trait_name);
- zend_string_release_ex(lc_trait_name, 0);
+ exclude_ce = zend_hash_find_ptr_lc(EG(class_table), class_name);
if (!exclude_ce || !(exclude_ce->ce_flags & ZEND_ACC_LINKED)) {
zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(class_name));
}
@@ -2619,9 +2610,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e
lcname = zend_string_tolower(cur_method_ref->method_name);
if (cur_method_ref->class_name) {
/* For all aliases with an explicit class name, resolve the class now. */
- lc_trait_name = zend_string_tolower(cur_method_ref->class_name);
- trait = zend_hash_find_ptr(EG(class_table), lc_trait_name);
- zend_string_release_ex(lc_trait_name, 0);
+ trait = zend_hash_find_ptr_lc(EG(class_table), cur_method_ref->class_name);
if (!trait || !(trait->ce_flags & ZEND_ACC_LINKED)) {
zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name));
}
diff --git a/Zend/zend_perf_stat.h b/Zend/zend_perf_stat.h
new file mode 100644
index 000000000000..447315de0133
--- /dev/null
+++ b/Zend/zend_perf_stat.h
@@ -0,0 +1,135 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine |
+ +----------------------------------------------------------------------+
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Ilija Tovilo |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_PERF_STAT_H
+#define ZEND_PERF_STAT_H
+
+#include "zend_portability.h"
+
+# if !(HAVE_FCNTL_H && HAVE_SYS_SELECT_H && HAVE_SYS_STAT_H && HAVE_SYS_TIME_H && HAVE_UNISTD_H)
+static void zend_perf_stat_enable(void) {}
+static void zend_perf_stat_disable(void) {}
+# else
+
+# include
+# include
+# include
+# include
+
+# include "Zend/zend.h"
+
+# define ZPS_CTL_FIFO_ENV "PERF_STAT_CTL_FIFO"
+# define ZPS_ACK_FIFO_ENV "PERF_STAT_ACK_FIFO"
+
+static int ctl_fd = -2;
+static int ack_fd = -2;
+
+static int zps_open_fifo(const char *env_name, int flags)
+{
+ const char *path = getenv(env_name);
+
+ if (path == NULL || path[0] == '\0') {
+ return -1;
+ }
+
+# ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+# endif
+ int fd = open(path, flags | O_NONBLOCK);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open fifo %s\n", path);
+ fflush(stderr);
+ zend_bailout();
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) != 0 || !S_ISFIFO(st.st_mode)) {
+ close(fd);
+ fprintf(stderr, "File %s is not a fifo\n", path);
+ fflush(stderr);
+ zend_bailout();
+ }
+ return fd;
+}
+
+static void zps_init(void)
+{
+ if (ctl_fd == -2) {
+ ctl_fd = zps_open_fifo(ZPS_CTL_FIFO_ENV, O_WRONLY);
+ }
+ if (ack_fd == -2) {
+ ack_fd = zps_open_fifo(ZPS_ACK_FIFO_ENV, O_RDONLY);
+ }
+}
+
+static void zps_wait_ack(void)
+{
+ if (ack_fd < 0) {
+ return;
+ }
+
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(ack_fd, &readfds);
+ if (select(ack_fd + 1, &readfds, NULL, NULL, &timeout) <= 0) {
+ return;
+ }
+
+ char ack[sizeof("ack\n") - 1];
+ ssize_t bytes_read;
+ do {
+ bytes_read = read(ack_fd, ack, sizeof(ack));
+ } while (bytes_read > 0);
+}
+
+static void zps_control(const char *command, size_t command_len)
+{
+ zps_init();
+
+ if (ctl_fd < 0) {
+ return;
+ }
+
+ if (write(ctl_fd, command, command_len) != (ssize_t) command_len) {
+ close(ctl_fd);
+ ctl_fd = -1;
+ return;
+ }
+
+ zps_wait_ack();
+}
+
+static void zend_perf_stat_enable(void)
+{
+ static const char command[] = "enable\n";
+
+ zps_control(command, sizeof(command) - 1);
+}
+
+static void zend_perf_stat_disable(void)
+{
+ static const char command[] = "disable\n";
+
+ zps_control(command, sizeof(command) - 1);
+}
+
+# endif
+#endif
diff --git a/ext/hash/hash.c b/ext/hash/hash.c
index e2af98c81ddf..74abf02acd81 100644
--- a/ext/hash/hash.c
+++ b/ext/hash/hash.c
@@ -102,9 +102,7 @@ static struct mhash_bc_entry mhash_to_hash[MHASH_NUM_ALGOS] = {
PHP_HASH_API const php_hash_ops *php_hash_fetch_ops(zend_string *algo) /* {{{ */
{
- zend_string *lower = zend_string_tolower(algo);
- const php_hash_ops *ops = zend_hash_find_ptr(&php_hash_hashtable, lower);
- zend_string_release(lower);
+ const php_hash_ops *ops = zend_hash_find_ptr_lc(&php_hash_hashtable, algo);
return ops;
}
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 83e933b01181..6f606d9d37d5 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -3847,9 +3847,7 @@ static zend_result preload_resolve_deps(preload_error *error, const zend_class_e
memset(error, 0, sizeof(preload_error));
if (ce->parent_name) {
- zend_string *key = zend_string_tolower(ce->parent_name);
- const zend_class_entry *parent = zend_hash_find_ptr(EG(class_table), key);
- zend_string_release(key);
+ const zend_class_entry *parent = zend_hash_find_ptr_lc(EG(class_table), ce->parent_name);
if (!parent) {
error->kind = "Unknown parent ";
error->name = ZSTR_VAL(ce->parent_name);
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index 6a47ec30c862..dcd9f7b126df 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -1484,7 +1484,6 @@ static zend_function *dbh_method_get(zend_object **object, zend_string *method_n
{
zend_function *fbc = NULL;
pdo_dbh_object_t *dbh_obj = php_pdo_dbh_fetch_object(*object);
- zend_string *lc_method_name;
if ((fbc = zend_std_get_method(object, method_name, key)) == NULL) {
/* not a pre-defined method, nor a user-defined method; check
@@ -1497,9 +1496,7 @@ static zend_function *dbh_method_get(zend_object **object, zend_string *method_n
}
}
- lc_method_name = zend_string_tolower(method_name);
- fbc = zend_hash_find_ptr(dbh_obj->inner->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH], lc_method_name);
- zend_string_release_ex(lc_method_name, 0);
+ fbc = zend_hash_find_ptr_lc(dbh_obj->inner->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH], method_name);
}
out:
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index a9f6b579977e..37c45cb02168 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -859,7 +859,6 @@ static void _function_string(smart_str *str, const zend_function *fptr, const ze
{
smart_str param_indent = {0};
zend_function *overwrites;
- zend_string *lc_name;
/* TBD: Repair indenting of doc comment (or is this to be done in the parser?)
* What's "wrong" is that any whitespace before the doc comment start is
@@ -885,13 +884,11 @@ static void _function_string(smart_str *str, const zend_function *fptr, const ze
if (fptr->common.scope != scope) {
smart_str_append_printf(str, ", inherits %s", ZSTR_VAL(fptr->common.scope->name));
} else if (fptr->common.scope->parent) {
- lc_name = zend_string_tolower(fptr->common.function_name);
- if ((overwrites = zend_hash_find_ptr(&fptr->common.scope->parent->function_table, lc_name)) != NULL) {
+ if ((overwrites = zend_hash_find_ptr_lc(&fptr->common.scope->parent->function_table, fptr->common.function_name)) != NULL) {
if (fptr->common.scope != overwrites->common.scope && !(overwrites->common.fn_flags & ZEND_ACC_PRIVATE)) {
smart_str_append_printf(str, ", overwrites %s", ZSTR_VAL(overwrites->common.scope->name));
}
}
- zend_string_release_ex(lc_name, 0);
}
}
if (fptr->common.prototype && fptr->common.prototype->common.scope) {
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c
index f49507827e2d..7efd8f159d59 100644
--- a/sapi/cgi/cgi_main.c
+++ b/sapi/cgi/cgi_main.c
@@ -95,6 +95,8 @@ int __riscosify_control = __RISCOSIFY_STRICT_UNIX_SPECS;
# endif
#endif
+#include "zend_perf_stat.h"
+
#ifndef PHP_WIN32
/* XXX this will need to change later when threaded fastcgi is implemented. shane */
static struct sigaction act, old_term, old_quit, old_int;
@@ -1733,6 +1735,7 @@ int main(int argc, char *argv[])
int warmup_repeats = 0;
int repeats = 1;
int benchmark = 0;
+ bool perf_enabled = false;
#ifdef HAVE_GETTIMEOFDAY
struct timeval start, end;
#else
@@ -2441,14 +2444,16 @@ consult the installation file that came with this distribution, or visit \n\
}
} /* end !cgi && !fastcgi */
-#ifdef HAVE_VALGRIND
if (warmup_repeats == 0) {
+ zend_perf_stat_enable();
+ perf_enabled = true;
+#ifdef HAVE_VALGRIND
CALLGRIND_START_INSTRUMENTATION;
# ifdef HAVE_VALGRIND_CACHEGRIND_H
CACHEGRIND_START_INSTRUMENTATION;
# endif
- }
#endif
+ }
/* request startup only after we've done all we can to
* get path_translated */
@@ -2568,6 +2573,10 @@ consult the installation file that came with this distribution, or visit \n\
SG(request_info).query_string = NULL;
}
+ if (perf_enabled) {
+ zend_perf_stat_disable();
+ perf_enabled = false;
+ }
#ifdef HAVE_VALGRIND
/* We're not interested in measuring shutdown */
CALLGRIND_STOP_INSTRUMENTATION;
diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c
index 9a05f8e68547..e5e573d1533c 100644
--- a/sapi/cli/php_cli.c
+++ b/sapi/cli/php_cli.c
@@ -1093,10 +1093,9 @@ static int do_cli(int argc, char **argv) /* {{{ */
case PHP_CLI_MODE_REFLECTION_EXT_INFO:
{
size_t len = strlen(reflection_what);
- char *lcname = zend_str_tolower_dup(reflection_what, len);
zend_module_entry *module;
- if ((module = zend_hash_str_find_ptr(&module_registry, lcname, len)) == NULL) {
+ if ((module = zend_hash_str_find_ptr_lc(&module_registry, reflection_what, len)) == NULL) {
if (!strcmp(reflection_what, "main")) {
display_ini_entries(NULL);
} else {
@@ -1107,7 +1106,6 @@ static int do_cli(int argc, char **argv) /* {{{ */
php_info_print_module(module);
}
- efree(lcname);
break;
}
diff --git a/sapi/phpdbg/phpdbg_bp.c b/sapi/phpdbg/phpdbg_bp.c
index 4dfa89d4b0fe..f4e4ef81af2f 100644
--- a/sapi/phpdbg/phpdbg_bp.c
+++ b/sapi/phpdbg/phpdbg_bp.c
@@ -602,13 +602,13 @@ PHPDBG_API int phpdbg_resolve_opline_break(phpdbg_breakopline_t *new_break) /* {
if (new_break->class_name != NULL) {
zend_class_entry *ce;
- if (!(ce = zend_hash_str_find_ptr(EG(class_table), zend_str_tolower_dup(new_break->class_name, new_break->class_len), new_break->class_len))) {
+ if (!(ce = zend_hash_str_find_ptr_lc(EG(class_table), new_break->class_name, new_break->class_len))) {
return FAILURE;
}
func_table = &ce->function_table;
}
- if (!(func = zend_hash_str_find_ptr(func_table, zend_str_tolower_dup(new_break->func_name, new_break->func_len), new_break->func_len))) {
+ if (!(func = zend_hash_str_find_ptr_lc(func_table, new_break->func_name, new_break->func_len))) {
if (new_break->class_name != NULL && new_break->func_name != NULL) {
phpdbg_error("Method %s doesn't exist in class %s", new_break->func_name, new_break->class_name);
return 2;
@@ -966,13 +966,7 @@ static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_symbol(zend_function *f
}
if (ops->function_name) {
- phpdbg_breakbase_t *brake;
- zend_string *fname = zend_string_tolower(ops->function_name);
-
- brake = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], fname);
-
- zend_string_release(fname);
- return brake;
+ return zend_hash_find_ptr_lc(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], ops->function_name);
} else {
return zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], ZEND_STRL("main"));
}
@@ -982,17 +976,11 @@ static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_method(zend_op_array *o
{
HashTable *class_table;
phpdbg_breakbase_t *brake = NULL;
- zend_string *class_lcname = zend_string_tolower(ops->scope->name);
-
- if ((class_table = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], class_lcname))) {
- zend_string *lcname = zend_string_tolower(ops->function_name);
-
- brake = zend_hash_find_ptr(class_table, lcname);
- zend_string_release(lcname);
+ if ((class_table = zend_hash_find_ptr_lc(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], ops->scope->name))) {
+ brake = zend_hash_find_ptr_lc(class_table, ops->function_name);
}
- zend_string_release(class_lcname);
return brake;
} /* }}} */
diff --git a/sapi/phpdbg/phpdbg_list.c b/sapi/phpdbg/phpdbg_list.c
index 505b04e81f91..3931fc9c1525 100644
--- a/sapi/phpdbg/phpdbg_list.c
+++ b/sapi/phpdbg/phpdbg_list.c
@@ -88,15 +88,12 @@ PHPDBG_LIST(method) /* {{{ */
if (phpdbg_safe_class_lookup(param->method.class, strlen(param->method.class), &ce) == SUCCESS) {
zend_function *function;
- char *lcname = zend_str_tolower_dup(param->method.name, strlen(param->method.name));
- if ((function = zend_hash_str_find_ptr(&ce->function_table, lcname, strlen(lcname)))) {
+ if ((function = zend_hash_str_find_ptr_lc(&ce->function_table, param->method.name, strlen(param->method.name)))) {
phpdbg_list_function(function);
} else {
phpdbg_error("Could not find %s::%s", param->method.class, param->method.name);
}
-
- efree(lcname);
} else {
phpdbg_error("Could not find the class %s", param->method.class);
}
diff --git a/sapi/phpdbg/tests/phpdbg_resolve_opline_break_leak.phpt b/sapi/phpdbg/tests/phpdbg_resolve_opline_break_leak.phpt
new file mode 100644
index 000000000000..411697cee021
--- /dev/null
+++ b/sapi/phpdbg/tests/phpdbg_resolve_opline_break_leak.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Resolving method/function opline breakpoints must not leak the lookup keys
+--PHPDBG--
+b Foo::bar#0
+b baz#0
+b Foo::nope#0
+b Nope::bar#0
+q
+--EXPECTF--
+[Successful compilation of %s]
+prompt> [Breakpoint #0 added at Foo::bar#0]
+prompt> [Breakpoint #1 added at baz#0]
+prompt> [Method nope doesn't exist in class Foo]
+prompt> [Pending breakpoint #3 at Nope::bar#0]
+prompt>
+--FILE--
+