Commit 39b66bae333 for php.net

commit 39b66bae333eabb173476d09de1106890bc94980
Author: Jorg Adam Sowa <jorg.sowa@gmail.com>
Date:   Fri Jul 3 01:53:33 2026 +0200

    Use zend_hash_find_ptr_lc() for case-insensitive name lookups (#22565)

    A number of call sites lowercased a class/function/method name into a
    temporary string solely to use it as a lookup key in the class table,
    function table, module registry, etc., then released it. Replace those
    with zend_hash_find_ptr_lc() / zend_hash_str_find_ptr_lc(), which
    lowercase into a stack buffer (for short names), perform the lookup, and
    free the temporary internally.

    This removes the manual tolower + release boilerplate at 17 sites and
    avoids a heap allocation for the common short-name case. No behavior
    change.

diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c
index d10b4d83fc3..0173d2ef47a 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 2dceac2512d..87acf073a2d 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 8df6a5599d3..848b9d209b2 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/ext/hash/hash.c b/ext/hash/hash.c
index e2af98c81dd..74abf02acd8 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 83e933b0118..6f606d9d37d 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 6a47ec30c86..dcd9f7b126d 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 a9f6b579977..37c45cb0216 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/cli/php_cli.c b/sapi/cli/php_cli.c
index 9a05f8e6854..e5e573d1533 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 4dfa89d4b0f..b9c3436280c 100644
--- a/sapi/phpdbg/phpdbg_bp.c
+++ b/sapi/phpdbg/phpdbg_bp.c
@@ -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 505b04e81f9..3931fc9c152 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);
 	}