From a5bba7f36696252042a83a98ea66bf5293f8104e Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 15 Aug 2026 06:27:40 +0900 Subject: [PATCH 1/4] fix(strlist_create): clear all elements The original code probably does not cause any problems because the caller will correctly fill elements and put the NULL terminator. However, when we zero-initialize the contents, we should also initialize the NULL terminator. --- lib/sh/stringlist.c | 4 ++-- pcomplete.c | 26 ++++++-------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/lib/sh/stringlist.c b/lib/sh/stringlist.c index 6e7a7836..203aa303 100644 --- a/lib/sh/stringlist.c +++ b/lib/sh/stringlist.c @@ -47,7 +47,7 @@ strlist_create (size_t n) { ret->list = strvec_create (n+1); ret->list_size = n; - for (i = 0; i < n; i++) + for (i = 0; i <= n; i++) ret->list[i] = (char *)NULL; } else @@ -168,7 +168,7 @@ strlist_append (STRINGLIST *m1, STRINGLIST *m2) if (len2) { - m1 = strlist_resize (m1, len1 + len2 + 1); + m1 = strlist_resize (m1, len1 + len2); for (i = 0, n = len1; i < len2; i++, n++) m1->list[n] = STRDUP (m2->list[i]); m1->list[n] = (char *)NULL; diff --git a/pcomplete.c b/pcomplete.c index d7ecc16c..a2b12c5f 100644 --- a/pcomplete.c +++ b/pcomplete.c @@ -311,27 +311,13 @@ STRINGLIST * completions_to_stringlist (char **matches) { STRINGLIST *sl; - int i, n; - size_t mlen; - - mlen = (matches == 0) ? 0 : strvec_len (matches); - sl = strlist_create (mlen + 1); + sl = strlist_create (0); if (matches == 0 || matches[0] == 0) return sl; - if (matches[1] == 0) - { - sl->list[0] = STRDUP (matches[0]); - sl->list[sl->list_len = 1] = (char *)NULL; - return sl; - } - - for (i = 1, n = 0; i < mlen; i++, n++) - sl->list[n] = STRDUP (matches[i]); - sl->list_len = n; - sl->list[n] = (char *)NULL; - + sl->list = strvec_copy (matches); + sl->list_len = sl->list_size = strvec_len (matches); return sl; } @@ -355,7 +341,7 @@ it_init_aliases (ITEMLIST *itp) } for (n = 0; alias_list[n]; n++) ; - sl = strlist_create (n+1); + sl = strlist_create (n); for (i = 0; i < n; i++) sl->list[i] = STRDUP (alias_list[i]->name); sl->list[n] = (char *)NULL; @@ -666,7 +652,7 @@ gen_matches_from_itemlist (ITEMLIST *itp, const char *text) } if (itp->slist == 0) return ((STRINGLIST *)NULL); - ret = strlist_create (itp->slist->list_len+1); + ret = strlist_create (itp->slist->list_len); sl = itp->slist; ntxt = bash_dequote_text (text); @@ -881,7 +867,7 @@ gen_wordlist_matches (COMPSPEC *cs, const char *text) dispose_words (l); nw = wlist_length (l2); - sl = strlist_create (nw + 1); + sl = strlist_create (nw); ntxt = bash_dequote_text (text); tlen = STRLEN (ntxt); From 1a21dfe8edb4bda704bbf7c505b6d5761414f48a Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Thu, 13 Aug 2026 05:25:30 +0900 Subject: [PATCH 2/4] perf(all_variables_matching_prefix): avoid making list of all visible variables --- variables.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/variables.c b/variables.c index eb803efc..51a5fc09 100644 --- a/variables.c +++ b/variables.c @@ -4495,26 +4495,37 @@ all_array_variables (void) } #endif /* ARRAY_VARS */ +static struct { + const char *prefix; + size_t plen; +} variable_matching_prefix_context; + +static int +variable_matching_prefix (SHELL_VAR *var) +{ + const char *prefix = variable_matching_prefix_context.prefix; + size_t plen = variable_matching_prefix_context.plen; + return (invisible_p (var) == 0 && (plen == 0 || STREQN (prefix, var->name, plen))); +} + char ** all_variables_matching_prefix (const char *prefix) { SHELL_VAR **varlist; char **rlist; int rind; - size_t plen, vind; + size_t vind; - plen = STRLEN (prefix); - varlist = all_visible_variables (); + variable_matching_prefix_context.prefix = prefix; + variable_matching_prefix_context.plen = STRLEN (prefix); + varlist = vapply (variable_matching_prefix); for (vind = 0; varlist && varlist[vind]; vind++) ; if (varlist == 0 || vind == 0) return ((char **)NULL); rlist = strvec_create (vind + 1); for (vind = rind = 0; varlist[vind]; vind++) - { - if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) - rlist[rind++] = savestring (varlist[vind]->name); - } + rlist[rind++] = savestring (varlist[vind]->name); rlist[rind] = (char *)0; free (varlist); From 3185e1a0c0d30b78c6aaaae683559b45e82cd064 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 15 Aug 2026 02:37:09 +0900 Subject: [PATCH 3/4] feat(ITEM_LIST::list_getter): accept a hint text The hint text can be used to reduce the size of the generated list. Filtering by the hint text is not mandatory. If the list is not too large, the list_getter function can safely ignore the hint_text argument. When list_getter fully performs the filtering by the hint text, list_getter can set flag LIST_PREFIXFILTERED so that the subsequent filtering at the caller can be skipped. --- pcomplete.c | 121 +++++++++++++++++++++++++++++++--------------------- pcomplete.h | 3 +- 2 files changed, 74 insertions(+), 50 deletions(-) diff --git a/pcomplete.c b/pcomplete.c index a2b12c5f..9dcaf90a 100644 --- a/pcomplete.c +++ b/pcomplete.c @@ -83,24 +83,24 @@ static void debug_printf (const char *, ...) __attribute__((__format__ (printf, static int it_init_joblist (ITEMLIST *, int); -static int it_init_aliases (ITEMLIST *); -static int it_init_arrayvars (ITEMLIST *); -static int it_init_bindings (ITEMLIST *); -static int it_init_builtins (ITEMLIST *); -static int it_init_disabled (ITEMLIST *); -static int it_init_enabled (ITEMLIST *); -static int it_init_exported (ITEMLIST *); -static int it_init_functions (ITEMLIST *); -static int it_init_helptopics (ITEMLIST *); -static int it_init_hostnames (ITEMLIST *); -static int it_init_jobs (ITEMLIST *); -static int it_init_running (ITEMLIST *); -static int it_init_stopped (ITEMLIST *); -static int it_init_keywords (ITEMLIST *); -static int it_init_signals (ITEMLIST *); -static int it_init_variables (ITEMLIST *); -static int it_init_setopts (ITEMLIST *); -static int it_init_shopts (ITEMLIST *); +static int it_init_aliases (ITEMLIST *, const char *); +static int it_init_arrayvars (ITEMLIST *, const char *); +static int it_init_bindings (ITEMLIST *, const char *); +static int it_init_builtins (ITEMLIST *, const char *); +static int it_init_disabled (ITEMLIST *, const char *); +static int it_init_enabled (ITEMLIST *, const char *); +static int it_init_exported (ITEMLIST *, const char *); +static int it_init_functions (ITEMLIST *, const char *); +static int it_init_helptopics (ITEMLIST *, const char *); +static int it_init_hostnames (ITEMLIST *, const char *); +static int it_init_jobs (ITEMLIST *, const char *); +static int it_init_running (ITEMLIST *, const char *); +static int it_init_stopped (ITEMLIST *, const char *); +static int it_init_keywords (ITEMLIST *, const char *); +static int it_init_signals (ITEMLIST *, const char *); +static int it_init_variables (ITEMLIST *, const char *); +static int it_init_setopts (ITEMLIST *, const char *); +static int it_init_shopts (ITEMLIST *, const char *); static int shouldexp_filterpat (char *); static char *preproc_filterpat (const char *, const char *); @@ -211,9 +211,10 @@ set_itemlist_dirty (ITEMLIST *it) } void -initialize_itemlist (ITEMLIST *itp) +initialize_itemlist (ITEMLIST *itp, const char *hint_text) { - (*itp->list_getter) (itp); + itp->flags &= ~LIST_PREFIXFILTERED; + (*itp->list_getter) (itp, hint_text); itp->flags |= LIST_INITIALIZED; itp->flags &= ~LIST_DIRTY; } @@ -325,7 +326,7 @@ completions_to_stringlist (char **matches) The caller is responsible for setting ITP->flags correctly. */ static int -it_init_aliases (ITEMLIST *itp) +it_init_aliases (ITEMLIST *itp, const char *) { #ifdef ALIAS alias_t **alias_list; @@ -379,7 +380,7 @@ init_itemlist_from_varlist (ITEMLIST *itp, SVFUNC *svfunc) } static int -it_init_arrayvars (ITEMLIST *itp) +it_init_arrayvars (ITEMLIST *itp, const char *) { #if defined (ARRAY_VARS) init_itemlist_from_varlist (itp, all_array_variables); @@ -390,7 +391,7 @@ it_init_arrayvars (ITEMLIST *itp) } static int -it_init_bindings (ITEMLIST *itp) +it_init_bindings (ITEMLIST *itp, const char *) { char **blist; STRINGLIST *sl; @@ -408,7 +409,7 @@ it_init_bindings (ITEMLIST *itp) } static int -it_init_builtins (ITEMLIST *itp) +it_init_builtins (ITEMLIST *itp, const char *) { STRINGLIST *sl; int i; @@ -425,7 +426,7 @@ it_init_builtins (ITEMLIST *itp) } static int -it_init_enabled (ITEMLIST *itp) +it_init_enabled (ITEMLIST *itp, const char *) { STRINGLIST *sl; int i; @@ -444,7 +445,7 @@ it_init_enabled (ITEMLIST *itp) } static int -it_init_disabled (ITEMLIST *itp) +it_init_disabled (ITEMLIST *itp, const char *) { STRINGLIST *sl; int i; @@ -463,14 +464,14 @@ it_init_disabled (ITEMLIST *itp) } static int -it_init_exported (ITEMLIST *itp) +it_init_exported (ITEMLIST *itp, const char *) { init_itemlist_from_varlist (itp, all_exported_variables); return 0; } static int -it_init_functions (ITEMLIST *itp) +it_init_functions (ITEMLIST *itp, const char *) { init_itemlist_from_varlist (itp, all_visible_functions); return 0; @@ -479,7 +480,7 @@ it_init_functions (ITEMLIST *itp) /* Like it_init_builtins, but includes everything the help builtin looks at, not just builtins with an active implementing function. */ static int -it_init_helptopics (ITEMLIST *itp) +it_init_helptopics (ITEMLIST *itp, const char *) { STRINGLIST *sl; int i; @@ -495,7 +496,7 @@ it_init_helptopics (ITEMLIST *itp) } static int -it_init_hostnames (ITEMLIST *itp) +it_init_hostnames (ITEMLIST *itp, const char *) { STRINGLIST *sl; @@ -549,25 +550,25 @@ it_init_joblist (ITEMLIST *itp, int jstate) } static int -it_init_jobs (ITEMLIST *itp) +it_init_jobs (ITEMLIST *itp, const char *) { return (it_init_joblist (itp, -1)); } static int -it_init_running (ITEMLIST *itp) +it_init_running (ITEMLIST *itp, const char *) { return (it_init_joblist (itp, 0)); } static int -it_init_stopped (ITEMLIST *itp) +it_init_stopped (ITEMLIST *itp, const char *) { return (it_init_joblist (itp, 1)); } static int -it_init_keywords (ITEMLIST *itp) +it_init_keywords (ITEMLIST *itp, const char *) { STRINGLIST *sl; int i; @@ -585,7 +586,7 @@ it_init_keywords (ITEMLIST *itp) } static int -it_init_signals (ITEMLIST *itp) +it_init_signals (ITEMLIST *itp, const char *) { STRINGLIST *sl; @@ -598,14 +599,14 @@ it_init_signals (ITEMLIST *itp) } static int -it_init_variables (ITEMLIST *itp) +it_init_variables (ITEMLIST *itp, const char *) { init_itemlist_from_varlist (itp, all_visible_variables); return 0; } static int -it_init_setopts (ITEMLIST *itp) +it_init_setopts (ITEMLIST *itp, const char *) { STRINGLIST *sl; @@ -618,7 +619,7 @@ it_init_setopts (ITEMLIST *itp) } static int -it_init_shopts (ITEMLIST *itp) +it_init_shopts (ITEMLIST *itp, const char *) { STRINGLIST *sl; @@ -642,28 +643,50 @@ gen_matches_from_itemlist (ITEMLIST *itp, const char *text) size_t tlen, i, n; char *ntxt; + ntxt = bash_dequote_text (text); + if ((itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) || (itp->flags & LIST_INITIALIZED) == 0) { if (itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) clean_itemlist (itp); if ((itp->flags & LIST_INITIALIZED) == 0) - initialize_itemlist (itp); + initialize_itemlist (itp, ntxt); } if (itp->slist == 0) - return ((STRINGLIST *)NULL); - ret = strlist_create (itp->slist->list_len); - sl = itp->slist; - - ntxt = bash_dequote_text (text); - tlen = STRLEN (ntxt); + { + FREE (ntxt); + return ((STRINGLIST *)NULL); + } - for (i = n = 0; i < sl->list_len; i++) + if (itp->flags & LIST_PREFIXFILTERED) { - if (tlen == 0 || STREQN (sl->list[i], ntxt, tlen)) - ret->list[n++] = STRDUP (sl->list[i]); + /* When the items are aready filtered by itp->list_getter using the + prefix text "ntxt", flag LIST_PREFIXFILTERED is set. In this case, we + can simply pass or copy the created string list. */ + if (itp-> flags & (LIST_DONTFREEMEMBERS|LIST_DONTFREE)) + ret = strlist_copy (itp->slist); + else + { + ret = itp->slist; + itp->slist = (STRINGLIST *)NULL; + clean_itemlist (itp); + } + } + else + { + ret = strlist_create (itp->slist->list_len); + sl = itp->slist; + + tlen = STRLEN (ntxt); + + for (i = n = 0; i < sl->list_len; i++) + { + if (tlen == 0 || STREQN (sl->list[i], ntxt, tlen)) + ret->list[n++] = STRDUP (sl->list[i]); + } + ret->list[ret->list_len = n] = (char *)NULL; } - ret->list[ret->list_len = n] = (char *)NULL; FREE (ntxt); return ret; diff --git a/pcomplete.h b/pcomplete.h index 6c0a0fbf..3c2dc0f3 100644 --- a/pcomplete.h +++ b/pcomplete.h @@ -88,7 +88,7 @@ typedef struct compspec { completions. */ typedef struct _list_of_items { int flags; - int (*list_getter) (struct _list_of_items *); /* function to call to get the list */ + int (*list_getter) (struct _list_of_items *, const char *); /* function to call to get the list */ STRINGLIST *slist; @@ -105,6 +105,7 @@ typedef struct _list_of_items { #define LIST_MUSTSORT 0x008 #define LIST_DONTFREE 0x010 #define LIST_DONTFREEMEMBERS 0x020 +#define LIST_PREFIXFILTERED 0x040 #define EMPTYCMD "_EmptycmD_" #define DEFAULTCMD "_DefaultCmD_" From 9c3169bd75dc9e64d82785e254b6b29df07bbef3 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 15 Aug 2026 03:39:12 +0900 Subject: [PATCH 4/4] perf(it_init_variables): use "all_variables_matching_prefix" --- pcomplete.c | 44 ++++++++++++++++++++------------------------ variables.c | 47 +++++++++++++++++++++++++++++++++++++---------- variables.h | 3 +++ 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/pcomplete.c b/pcomplete.c index 9dcaf90a..dbc1e78f 100644 --- a/pcomplete.c +++ b/pcomplete.c @@ -72,7 +72,7 @@ #endif #define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) -typedef SHELL_VAR **SVFUNC (void); +typedef char **STRVECFUNC (const char *); extern STRING_INT_ALIST word_token_alist[]; extern char *signal_names[]; @@ -105,7 +105,7 @@ static int it_init_shopts (ITEMLIST *, const char *); static int shouldexp_filterpat (char *); static char *preproc_filterpat (const char *, const char *); -static void init_itemlist_from_varlist (ITEMLIST *, SVFUNC *); +static void init_itemlist_from_strvec (ITEMLIST *, STRVECFUNC *, const char *); static STRINGLIST *gen_matches_from_itemlist (ITEMLIST *, const char *); static STRINGLIST *gen_action_completions (COMPSPEC *, const char *); @@ -356,34 +356,30 @@ it_init_aliases (ITEMLIST *itp, const char *) } static void -init_itemlist_from_varlist (ITEMLIST *itp, SVFUNC *svfunc) +init_itemlist_from_strvec (ITEMLIST *itp, STRVECFUNC *strvecfunc, const char *prefix) { - SHELL_VAR **vlist; + char **svec; STRINGLIST *sl; - int i; - size_t n; - vlist = (*svfunc) (); - if (vlist == 0) + svec = (*strvecfunc) (prefix); + if (svec == 0) { itp->slist = (STRINGLIST *)NULL; return; - } - for (n = 0; vlist[n]; n++) - ; - sl = strlist_create (n+1); - for (i = 0; i < n; i++) - sl->list[i] = savestring (vlist[i]->name); - sl->list[sl->list_len = n] = (char *)NULL; + } + + sl = strlist_create (0); + sl->list = svec; + sl->list_len = sl->list_size = strvec_len(svec); itp->slist = sl; - free (vlist); + itp->flags |= LIST_PREFIXFILTERED; } static int -it_init_arrayvars (ITEMLIST *itp, const char *) +it_init_arrayvars (ITEMLIST *itp, const char *prefix) { #if defined (ARRAY_VARS) - init_itemlist_from_varlist (itp, all_array_variables); + init_itemlist_from_strvec (itp, all_array_variables_matching_prefix, prefix); return 1; #else return 0; @@ -464,16 +460,16 @@ it_init_disabled (ITEMLIST *itp, const char *) } static int -it_init_exported (ITEMLIST *itp, const char *) +it_init_exported (ITEMLIST *itp, const char *prefix) { - init_itemlist_from_varlist (itp, all_exported_variables); + init_itemlist_from_strvec (itp, all_exported_variables_matching_prefix, prefix); return 0; } static int -it_init_functions (ITEMLIST *itp, const char *) +it_init_functions (ITEMLIST *itp, const char *prefix) { - init_itemlist_from_varlist (itp, all_visible_functions); + init_itemlist_from_strvec (itp, all_functions_matching_prefix, prefix); return 0; } @@ -599,9 +595,9 @@ it_init_signals (ITEMLIST *itp, const char *) } static int -it_init_variables (ITEMLIST *itp, const char *) +it_init_variables (ITEMLIST *itp, const char *prefix) { - init_itemlist_from_varlist (itp, all_visible_variables); + init_itemlist_from_strvec (itp, all_variables_matching_prefix, prefix); return 0; } diff --git a/variables.c b/variables.c index 51a5fc09..c10e7cec 100644 --- a/variables.c +++ b/variables.c @@ -4496,29 +4496,32 @@ all_array_variables (void) #endif /* ARRAY_VARS */ static struct { + sh_var_map_func_t *func; const char *prefix; size_t plen; -} variable_matching_prefix_context; +} name_matching_prefix_context; static int -variable_matching_prefix (SHELL_VAR *var) +name_matching_prefix (SHELL_VAR *var) { - const char *prefix = variable_matching_prefix_context.prefix; - size_t plen = variable_matching_prefix_context.plen; - return (invisible_p (var) == 0 && (plen == 0 || STREQN (prefix, var->name, plen))); + sh_var_map_func_t *func = name_matching_prefix_context.func; + const char *prefix = name_matching_prefix_context.prefix; + size_t plen = name_matching_prefix_context.plen; + return func(var) && (plen == 0 || STREQN (prefix, var->name, plen)); } -char ** -all_variables_matching_prefix (const char *prefix) +static char ** +all_names_matching_prefix (SHELL_VAR ** (*apply)(sh_var_map_func_t *), sh_var_map_func_t *func, const char *prefix) { SHELL_VAR **varlist; char **rlist; int rind; size_t vind; - variable_matching_prefix_context.prefix = prefix; - variable_matching_prefix_context.plen = STRLEN (prefix); - varlist = vapply (variable_matching_prefix); + name_matching_prefix_context.func = func; + name_matching_prefix_context.prefix = prefix; + name_matching_prefix_context.plen = STRLEN (prefix); + varlist = apply (name_matching_prefix); for (vind = 0; varlist && varlist[vind]; vind++) ; if (varlist == 0 || vind == 0) @@ -4532,6 +4535,30 @@ all_variables_matching_prefix (const char *prefix) return rlist; } +char ** +all_variables_matching_prefix (const char *prefix) +{ + return all_names_matching_prefix (vapply, visible_var, prefix); +} + +char ** +all_exported_variables_matching_prefix (const char *prefix) +{ + return all_names_matching_prefix (vapply, visible_and_exported, prefix); +} + +char ** +all_array_variables_matching_prefix (const char *prefix) +{ + return all_names_matching_prefix (vapply, visible_array_vars, prefix); +} + +char ** +all_functions_matching_prefix (const char *prefix) +{ + return all_names_matching_prefix (fapply, visible_var, prefix); +} + /* **************************************************************** */ /* */ /* Managing temporary variable scopes */ diff --git a/variables.h b/variables.h index a8a0214f..3bbea5d3 100644 --- a/variables.h +++ b/variables.h @@ -329,6 +329,9 @@ extern SHELL_VAR **all_local_variables (int); extern SHELL_VAR **all_array_variables (void); #endif extern char **all_variables_matching_prefix (const char *); +extern char **all_exported_variables_matching_prefix (const char *); +extern char **all_array_variables_matching_prefix (const char *); +extern char **all_functions_matching_prefix (const char *); extern char **make_var_array (HASH_TABLE *); extern char **add_or_supercede_exported_var (char *, int);