Commit ea9454d3528 for php

commit ea9454d35281856801433760c392b294059741ce
Author: Weilin Du <weilindu@php.net>
Date:   Fri Oct 9 21:57:40 2026 +0800

    ext/readline: Refactor CLI readline completion generators

    Replace strcpy() with length-aware memcpy() and
    check malloc() failures.

diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c
index 973aea10611..3f435b82c78 100644
--- a/ext/readline/readline_cli.c
+++ b/ext/readline/readline_cli.c
@@ -412,7 +412,15 @@ static int cli_is_valid_code(char *code, size_t len, zend_string **prompt) /* {{
 }
 /* }}} */

-static char *cli_completion_generator_ht(const char *text, size_t textlen, int *state, HashTable *ht, void **pData) /* {{{ */
+typedef enum {
+	CLI_COMPLETION_VARIABLE,
+	CLI_COMPLETION_INI,
+	CLI_COMPLETION_FUNCTION,
+	CLI_COMPLETION_CONSTANT,
+	CLI_COMPLETION_CLASS,
+} cli_completion_type;
+
+static char *cli_completion_generator_ht(const char *text, size_t textlen, int *state, HashTable *ht, cli_completion_type type) /* {{{ */
 {
 	zend_string *name;
 	zend_ulong number;
@@ -421,14 +429,48 @@ static char *cli_completion_generator_ht(const char *text, size_t textlen, int *
 		zend_hash_internal_pointer_reset(ht);
 		(*state)++;
 	}
-	while(zend_hash_has_more_elements(ht) == SUCCESS) {
+	while (zend_hash_has_more_elements(ht) == SUCCESS) {
 		zend_hash_get_current_key(ht, &name, &number);
 		if (!textlen || !strncmp(ZSTR_VAL(name), text, textlen)) {
-			if (pData) {
-				*pData = zend_hash_get_current_data_ptr(ht);
+			char prefix = '\0';
+			char append_character = '\0';
+
+			switch (type) {
+				case CLI_COMPLETION_VARIABLE:
+					prefix = '$';
+					break;
+				case CLI_COMPLETION_INI:
+					prefix = '#';
+					append_character = '=';
+					break;
+				case CLI_COMPLETION_FUNCTION: {
+					zend_function *func = zend_hash_get_current_data_ptr(ht);
+					name = func->common.function_name;
+					append_character = '(';
+					break;
+				}
+				case CLI_COMPLETION_CLASS: {
+					zend_class_entry *ce = zend_hash_get_current_data_ptr(ht);
+					name = ce->name;
+					break;
+				}
+				case CLI_COMPLETION_CONSTANT:
+					break;
 			}
+
 			zend_hash_move_forward(ht);
-			return ZSTR_VAL(name);
+			/* The line editing library takes ownership of the candidate. */
+			size_t prefix_len = prefix ? 1 : 0;
+			char *retval = malloc(prefix_len + ZSTR_LEN(name) + 1);
+			if (!retval) {
+				return NULL;
+			}
+			if (prefix) {
+				retval[0] = prefix;
+			}
+			memcpy(retval + prefix_len, ZSTR_VAL(name), ZSTR_LEN(name) + 1);
+			rl_completion_append_character = append_character;
+			return retval;
 		}
 		if (zend_hash_move_forward(ht) == FAILURE) {
 			break;
@@ -438,71 +480,6 @@ static char *cli_completion_generator_ht(const char *text, size_t textlen, int *
 	return NULL;
 } /* }}} */

-static char *cli_completion_generator_var(const char *text, size_t textlen, int *state) /* {{{ */
-{
-	char *retval, *tmp;
-	zend_array *symbol_table = &EG(symbol_table);
-
-	tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, symbol_table, NULL);
-	if (retval) {
-		retval = malloc(strlen(tmp) + 2);
-		retval[0] = '$';
-		strcpy(&retval[1], tmp);
-		rl_completion_append_character = '\0';
-	}
-	return retval;
-} /* }}} */
-
-static char *cli_completion_generator_ini(const char *text, size_t textlen, int *state) /* {{{ */
-{
-	char *retval, *tmp;
-
-	tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL);
-	if (retval) {
-		retval = malloc(strlen(tmp) + 2);
-		retval[0] = '#';
-		strcpy(&retval[1], tmp);
-		rl_completion_append_character = '=';
-	}
-	return retval;
-} /* }}} */
-
-static char *cli_completion_generator_func(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
-{
-	zend_function *func;
-	char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func);
-	if (retval) {
-		rl_completion_append_character = '(';
-		retval = strdup(ZSTR_VAL(func->common.function_name));
-	}
-
-	return retval;
-} /* }}} */
-
-static char *cli_completion_generator_class(const char *text, size_t textlen, int *state) /* {{{ */
-{
-	zend_class_entry *ce;
-	char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&ce);
-	if (retval) {
-		rl_completion_append_character = '\0';
-		retval = strdup(ZSTR_VAL(ce->name));
-	}
-
-	return retval;
-} /* }}} */
-
-static char *cli_completion_generator_define(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
-{
-	zend_class_entry **pce;
-	char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce);
-	if (retval) {
-		rl_completion_append_character = '\0';
-		retval = strdup(retval);
-	}
-
-	return retval;
-} /* }}} */
-
 static int cli_completion_state;

 static char *cli_completion_generator(const char *text, int index) /* {{{ */
@@ -523,9 +500,9 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */
 		cli_completion_state = 0;
 	}
 	if (text[0] == '$') {
-		retval = cli_completion_generator_var(text, textlen, &cli_completion_state);
+		retval = cli_completion_generator_ht(text + 1, textlen - 1, &cli_completion_state, &EG(symbol_table), CLI_COMPLETION_VARIABLE);
 	} else if (text[0] == '#' && text[1] != '[') {
-		retval = cli_completion_generator_ini(text, textlen, &cli_completion_state);
+		retval = cli_completion_generator_ht(text + 1, textlen - 1, &cli_completion_state, EG(ini_directives), CLI_COMPLETION_INI);
 	} else {
 		char *lc_text;
 		const char *class_name_end;
@@ -552,21 +529,21 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */
 		switch (cli_completion_state) {
 			case 0:
 			case 1:
-				retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, ce ? &ce->function_table : EG(function_table));
+				retval = cli_completion_generator_ht(lc_text, textlen, &cli_completion_state, ce ? &ce->function_table : EG(function_table), CLI_COMPLETION_FUNCTION);
 				if (retval) {
 					break;
 				}
 				ZEND_FALLTHROUGH;
 			case 2:
 			case 3:
-				retval = cli_completion_generator_define(constant_text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants));
+				retval = cli_completion_generator_ht(constant_text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants), CLI_COMPLETION_CONSTANT);
 				if (retval || ce) {
 					break;
 				}
 				ZEND_FALLTHROUGH;
 			case 4:
 			case 5:
-				retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state);
+				retval = cli_completion_generator_ht(lc_text, textlen, &cli_completion_state, EG(class_table), CLI_COMPLETION_CLASS);
 				break;
 			default:
 				break;