From 911ae06ca9e2b6e9778bfb2e0d8c04bfcc568beb Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Tue, 16 Jan 2018 09:57:29 -0500 Subject: [PATCH] commit bash-20180112 snapshot --- CWRU/CWRU.chlog | 48 ++++++++++++++++++ MANIFEST | 1 + alias.c | 4 ++ alias.h | 3 ++ builtins/complete.def | 10 ++++ builtins/declare.def | 4 +- doc/bashref.texi | 2 +- parse.y | 56 +++++++++++++++++++-- pcomplete.c | 110 +++++++++++++++++++++++++++++++++++++----- pcomplete.h | 11 +++++ tests/alias.right | 12 +++++ tests/alias.tests | 1 + tests/alias4.sub | 61 +++++++++++++++++++++++ 13 files changed, 302 insertions(+), 21 deletions(-) create mode 100644 tests/alias4.sub diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 69ba0ffb..d6e8e0f1 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -14800,3 +14800,51 @@ subst.c 'G' to the options list if W_CHKLOCAL is set in the word's flags. This makes builtins like `readonly' that modify local variables in a function behave the same for scalar and array variables + + 1/11 + ---- +parse.y + - shell_getc: move code that decides whether to append a space to an + alias expansion here from mk_alexpansion, so we can inhibit adding + a space if we're currently parsing a single or double quoted string + + 1/12 + ---- + +parse.y + - clear_string_list_expander: take a pointer to an alias that's about + to be freed and make sure there aren't any pointers to it in the + list of pushed strings. If there are, zero it out in the pushed + string list to avoid referencing freed memory in pop_string() + +alias.c + - free_alias_data: if an alias being freed is currently being expanded, + call clear_string_list_expander to remove references to it from the + list of pushed strings + + 1/14 + ---- +pcomplib.c + - progcomp_search: add code to look up an alias for the CMD argument + and return the completions for the first word of that alias if one + is found. Just a start at completing aliases, a much-requested + feature + +pcomplete.h + - COPT_LASTUSER: last flag value used by user-settable completion + options + - PCOMP_RETRYFAIL, PCOMP_NOTFOUND: new #defines, possible return values + from programmable_completions in FOUNDP argument. Moved RETRYFAIL + define here from pcomplete.c to avoid collisions with user-settable + option values (COPT_*) + + 1/15 + ---- +pcomplete.c + - programmable_completions: if we don't find any completions for a + command, and RETRY is 0, see if the command is a defined alias, + expand it, and try to expand the first word of the value as a + command, and find any programmable completions for it. Here right + now, could be moved to attempt_shell_completion later if we need + to do more analysis of the expanded line. We'll see how it works + in practice. diff --git a/MANIFEST b/MANIFEST index 658cc7ba..5f635812 100644 --- a/MANIFEST +++ b/MANIFEST @@ -820,6 +820,7 @@ tests/alias.tests f tests/alias1.sub f tests/alias2.sub f tests/alias3.sub f +tests/alias4.sub f tests/alias.right f tests/appendop.tests f tests/appendop1.sub f diff --git a/alias.c b/alias.c index 8b3f2289..74ae8700 100644 --- a/alias.c +++ b/alias.c @@ -158,6 +158,10 @@ free_alias_data (data) register alias_t *a; a = (alias_t *)data; + + if (a->flags & AL_BEINGEXPANDED) + clear_string_list_expander (a); /* call back to the parser */ + free (a->value); free (a->name); free (data); diff --git a/alias.h b/alias.h index 4d0075f8..87174a32 100644 --- a/alias.h +++ b/alias.h @@ -67,4 +67,7 @@ extern char *alias_expand_word __P((char *)); /* Return a new line, with any aliases expanded. */ extern char *alias_expand __P((char *)); +/* Helper definition for the parser */ +extern void clear_string_list_expander __P((alias_t *)); + #endif /* _ALIAS_H_ */ diff --git a/builtins/complete.def b/builtins/complete.def index 407fa348..3ca8c1fa 100644 --- a/builtins/complete.def +++ b/builtins/complete.def @@ -681,6 +681,8 @@ compgen_builtin (list) COMPSPEC *cs; STRINGLIST *sl; char *word, **matches; + char *old_line; + int old_ind; if (list == 0) return (EXECUTION_SUCCESS); @@ -721,7 +723,15 @@ compgen_builtin (list) cs->filterpat = STRDUP (Xarg); rval = EXECUTION_FAILURE; + + /* probably don't have to save these, just being safe */ + old_line = pcomp_line; + old_ind = pcomp_ind; + pcomp_line = (char *)NULL; + pcomp_ind = 0; sl = gen_compspec_completions (cs, "compgen", word, 0, 0, 0); + pcomp_line = old_line; + pcomp_ind = old_ind; /* If the compspec wants the bash default completions, temporarily turn off programmable completion and call the bash completion code. */ diff --git a/builtins/declare.def b/builtins/declare.def index 8a327458..b56f2488 100644 --- a/builtins/declare.def +++ b/builtins/declare.def @@ -40,11 +40,11 @@ Options which set attributes: -a to make NAMEs indexed arrays (if supported) -A to make NAMEs associative arrays (if supported) -i to make NAMEs have the `integer' attribute - -l to convert NAMEs to lower case on assignment + -l to convert the value of each NAME to lower case on assignment -n make NAME a reference to the variable named by its value -r to make NAMEs readonly -t to make NAMEs have the `trace' attribute - -u to convert NAMEs to upper case on assignment + -u to convert the value of each NAME to upper case on assignment -x to make NAMEs export Using `+' instead of `-' turns off the given attribute. diff --git a/doc/bashref.texi b/doc/bashref.texi index 55a47c27..80a6930e 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -4373,7 +4373,7 @@ Remove a trailing @var{delim} (default newline) from each line read. @item -u Read lines from file descriptor @var{fd} instead of the standard input. @item -C -Evaluate @var{callback} each time @var{quantum}P lines are read. +Evaluate @var{callback} each time @var{quantum} lines are read. The @option{-c} option specifies @var{quantum}. @item -c Specify the number of lines read between each call to @var{callback}. diff --git a/parse.y b/parse.y index 079fa6ea..f75d8d8e 100644 --- a/parse.y +++ b/parse.y @@ -95,6 +95,8 @@ typedef void *alias_t; #define RE_READ_TOKEN -99 #define NO_EXPANSION -100 +#define END_ALIAS -2 + #ifdef DEBUG # define YYDEBUG 1 #else @@ -1967,6 +1969,23 @@ parser_restore_alias () #endif } +#if defined (ALIAS) +/* Before freeing AP, make sure that there aren't any cases of pointer + aliasing that could cause us to reference freed memory later on. */ +void +clear_string_list_expander (ap) + alias_t *ap; +{ + register STRING_SAVER *t; + + for (t = pushed_string_list; t; t = t->next) + { + if (t->expander && t->expander == ap) + t->expander = 0; + } +} +#endif + void clear_shell_input_line () { @@ -2500,6 +2519,30 @@ next_alias_char: parsing an alias, we have just saved one (push_string, when called by the parse_dparen code) In this case, just go on as well. The PSH_SOURCE case is handled below. */ + + /* If we're at the end of an alias expansion add a space to make sure that + the alias remains marked as being in use while we expand its last word. + This makes sure that pop_string doesn't mark the alias as not in use + before the string resulting from the alias expansion is tokenized and + checked for alias expansion, preventing recursion. At this point, the + last character in shell_input_line is the last character of the alias + expansion. We test that last character to determine whether or not to + return the space that will delimit the token and postpone the pop_string. + This set of conditions duplicates what used to be in mk_alexpansion () + below, with the addition that we don't add a space if we're currently + reading a quoted string. */ +#ifndef OLD_ALIAS_HACK + if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE && + shell_input_line_index > 0 && + shell_input_line[shell_input_line_index-1] != ' ' && + shell_input_line[shell_input_line_index-1] != '\n' && + shellmeta (shell_input_line[shell_input_line_index-1]) == 0 && + (current_delimiter (dstack) != '\'' && current_delimiter (dstack) != '"')) + { + return ' '; /* END_ALIAS */ + } +#endif + pop_alias: if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE) { @@ -2518,10 +2561,9 @@ pop_alias: /* What do we do here if we're expanding an alias whose definition includes an escaped newline? If that's the last character in the alias expansion, we just pop the pushed string list (recall that - we inhibit the appending of a space in mk_alexpansion() if newline - is the last character). If it's not the last character, we need - to consume the quoted newline and move to the next character in - the expansion. */ + we inhibit the appending of a space if newline is the last + character). If it's not the last character, we need to consume the + quoted newline and move to the next character in the expansion. */ #if defined (ALIAS) if (expanding_alias () && shell_input_line[shell_input_line_index+1] == '\0') { @@ -2829,13 +2871,15 @@ mk_alexpansion (s) l = strlen (s); r = xmalloc (l + 2); strcpy (r, s); +#ifdef OLD_ALIAS_HACK /* If the last character in the alias is a newline, don't add a trailing space to the expansion. Works with shell_getc above. */ /* Need to do something about the case where the alias expansion contains an unmatched quoted string, since appending this space affects the subsequent output. */ - if (r[l - 1] != ' ' && r[l - 1] != '\n' && shellmeta(r[l - 1]) == 0) + if (l > 0 && r[l - 1] != ' ' && r[l - 1] != '\n' && shellmeta(r[l - 1]) == 0) r[l++] = ' '; +#endif r[l] = '\0'; return r; } @@ -2856,12 +2900,14 @@ alias_expand_token (tokstr) if (ap && (ap->flags & AL_BEINGEXPANDED)) return (NO_EXPANSION); +#ifdef OLD_ALIAS_HACK /* mk_alexpansion puts an extra space on the end of the alias expansion, so the lookahead by the parser works right (the alias needs to remain `in use' while parsing its last word to avoid alias recursion for something like "alias echo=echo"). If this gets changed, make sure the code in shell_getc that deals with reaching the end of an expanded alias is changed with it. */ +#endif expanded = ap ? mk_alexpansion (ap->value) : (char *)NULL; if (expanded) diff --git a/pcomplete.c b/pcomplete.c index 310a69fa..7a5f102f 100644 --- a/pcomplete.c +++ b/pcomplete.c @@ -1,6 +1,6 @@ /* pcomplete.c - functions to generate lists of matches for programmable completion. */ -/* Copyright (C) 1999-2012 Free Software Foundation, Inc. +/* Copyright (C) 1999-2018 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -71,8 +71,6 @@ #include #include -#define PCOMP_RETRYFAIL 256 - #ifdef STRDUP # undef STRDUP #endif @@ -153,6 +151,10 @@ static int progcomp_debug = 0; int prog_completion_enabled = 1; +#ifdef ALIAS +int progcomp_alias = 0; /* unavailable to user code for now */ +#endif + /* These are used to manage the arrays of strings for possible completions. */ ITEMLIST it_aliases = { 0, it_init_aliases, (STRINGLIST *)0 }; ITEMLIST it_arrayvars = { LIST_DYNAMIC, it_init_arrayvars, (STRINGLIST *)0 }; @@ -183,6 +185,9 @@ COMPSPEC *pcomp_curcs; const char *pcomp_curcmd; const char *pcomp_curtxt; +char *pcomp_line; +int pcomp_ind; + #ifdef DEBUG /* Debugging code */ static void @@ -1380,14 +1385,14 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) /* If we have a command or function to execute, we need to first break the command line into individual words, find the number of words, and find the word in the list containing the word to be completed. */ - line = substring (rl_line_buffer, start, end); + line = substring (pcomp_line, start, end); llen = end - start; #ifdef DEBUG debug_printf ("command_line_to_word_list (%s, %d, %d, %p, %p)", - line, llen, rl_point - start, &nw, &cw); + line, llen, pcomp_ind - start, &nw, &cw); #endif - lwords = command_line_to_word_list (line, llen, rl_point - start, &nw, &cw); + lwords = command_line_to_word_list (line, llen, pcomp_ind - start, &nw, &cw); /* If we skipped a NULL word at the beginning of the line, add it back */ if (lwords && lwords->word && cmd[0] == 0 && lwords->word->word[0] != 0) { @@ -1414,7 +1419,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) if (cs->funcname) { foundf = 0; - tmatches = gen_shell_function_matches (cs, cmd, word, line, rl_point - start, lwords, nw, cw, &foundf); + tmatches = gen_shell_function_matches (cs, cmd, word, line, pcomp_ind - start, lwords, nw, cw, &foundf); if (foundf != 0) found = foundf; if (tmatches) @@ -1434,7 +1439,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) if (cs->command) { - tmatches = gen_command_matches (cs, cmd, word, line, rl_point - start, lwords, nw, cw); + tmatches = gen_command_matches (cs, cmd, word, line, pcomp_ind - start, lwords, nw, cw); if (tmatches) { #ifdef DEBUG @@ -1608,7 +1613,8 @@ gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs) /* The driver function for the programmable completion code. Returns a list of matches for WORD, which is an argument to command CMD. START and END - bound the command currently being completed in rl_line_buffer. */ + bound the command currently being completed in pcomp_line (usually + rl_line_buffer). */ char ** programmable_completions (cmd, word, start, end, foundp) const char *cmd; @@ -1619,26 +1625,95 @@ programmable_completions (cmd, word, start, end, foundp) STRINGLIST *ret; char **rmatches, *t; int found, retry, count; + char *ocmd; + int oend; +#if defined (ALIAS) + alias_t *al; +#endif lastcs = 0; found = count = 0; + pcomp_line = rl_line_buffer; + pcomp_ind = rl_point; + + ocmd = (char *)cmd; + oend = end; + do { retry = 0; /* We look at the basename of CMD if the full command does not have an associated COMPSPEC. */ - ret = gen_progcomp_completions (cmd, cmd, word, start, end, &found, &retry, &lastcs); + ret = gen_progcomp_completions (ocmd, ocmd, word, start, oend, &found, &retry, &lastcs); if (found == 0) { - t = strrchr (cmd, '/'); + t = strrchr (ocmd, '/'); if (t && *(++t)) - ret = gen_progcomp_completions (t, cmd, word, start, end, &found, &retry, &lastcs); + ret = gen_progcomp_completions (t, ocmd, word, start, oend, &found, &retry, &lastcs); } if (found == 0) - ret = gen_progcomp_completions (DEFAULTCMD, cmd, word, start, end, &found, &retry, &lastcs); + ret = gen_progcomp_completions (DEFAULTCMD, ocmd, word, start, oend, &found, &retry, &lastcs); + +#if defined (ALIAS) + /* Look up any alias for CMD, try to gen completions for it */ + /* Look up the alias, find the value, build a new line replacing CMD + with that value, offsetting PCOMP_IND and END appropriately, reset + PCOMP_LINE to the new line and OCMD with the new command name, then + call gen_progcomp_completions again. We could use alias_expand for + this, but it does more (and less) than we need right now. */ + if (found == 0 && retry == 0 && progcomp_alias && (al = find_alias (ocmd))) + { + char *ncmd, *nline, *ntxt; + int ind, lendiff; + size_t nlen, olen, llen; + + /* We found an alias for OCMD. Take the value and build a new line */ + ntxt = al->value; + nlen = strlen (ntxt); + if (nlen == 0) + break; + olen = strlen (ocmd); + lendiff = nlen - olen; /* can be negative */ + llen = strlen (pcomp_line); + + nline = (char *)xmalloc (llen + lendiff + 1); + if (start > 0) + strncpy (nline, pcomp_line, start); + strncpy (nline + start, ntxt, nlen); + strcpy (nline + start + nlen, pcomp_line + start + olen); + + /* Find the first word of the alias value and use that as OCMD. We + don't check the alias value to see whether it begins with a valid + command name, so this can be fooled. */ + ind = skip_to_delim (ntxt, 0, "()<>;&| \t\n", SD_NOJMP|SD_COMPLETE); /*)*/ + if (ind > 0) + ncmd = substring (ntxt, 0, ind); + else + { + free (nline); + break; /* will free pcomp_line and ocmd later */ + } + + /* Adjust PCOMP_IND and OEND appropriately */ + pcomp_ind += lendiff; + oend += lendiff; + + /* Set up values with new line. WORD stays the same. */ + if (ocmd != cmd) + free (ocmd); + if (pcomp_line != rl_line_buffer) + free (pcomp_line); + + ocmd = ncmd; + pcomp_line = nline; + + /* And go back and start over. */ + retry = 1; + } +#endif /* ALIAS */ count++; @@ -1650,6 +1725,11 @@ programmable_completions (cmd, word, start, end, foundp) } while (retry); + if (pcomp_line != rl_line_buffer) + free (pcomp_line); + if (ocmd != cmd) + free (ocmd); + if (ret) { rmatches = ret->list; @@ -1664,6 +1744,10 @@ programmable_completions (cmd, word, start, end, foundp) if (lastcs) /* XXX - should be while? */ compspec_dispose (lastcs); + /* XXX restore pcomp_line and pcomp_ind? */ + pcomp_line = rl_line_buffer; + pcomp_ind = rl_point; + return (rmatches); } diff --git a/pcomplete.h b/pcomplete.h index 0514245a..2ae8224e 100644 --- a/pcomplete.h +++ b/pcomplete.h @@ -77,6 +77,12 @@ typedef struct compspec { #define COPT_PLUSDIRS (1<<7) #define COPT_NOSORT (1<<8) +#define COPT_LASTUSER COPT_NOSORT + +#define PCOMP_RETRYFAIL (COPT_LASTUSER << 1) +#define PCOMP_NOTFOUND (COPT_LASTUSER << 2) + + /* List of items is used by the code that implements the programmable completions. */ typedef struct _list_of_items { @@ -103,7 +109,12 @@ typedef struct _list_of_items { #define DEFAULTCMD "_DefaultCmD_" extern HASH_TABLE *prog_completes; + +extern char *pcomp_line; +extern int pcomp_ind; + extern int prog_completion_enabled; +extern int progcomp_alias; /* Not all of these are used yet. */ extern ITEMLIST it_aliases; diff --git a/tests/alias.right b/tests/alias.right index cab07a98..3ab9a717 100644 --- a/tests/alias.right +++ b/tests/alias.right @@ -18,3 +18,15 @@ one two three four +Error: bar +ok 1 +ok 2 +text +whoops: nullalias +foo +a +a b +a b +a a b +ok 3 +ok 4 diff --git a/tests/alias.tests b/tests/alias.tests index 9442e526..6aa1218b 100644 --- a/tests/alias.tests +++ b/tests/alias.tests @@ -39,3 +39,4 @@ unalias foo bar baz ${THIS_SH} ./alias1.sub ${THIS_SH} ./alias2.sub ${THIS_SH} ./alias3.sub +${THIS_SH} ./alias4.sub diff --git a/tests/alias4.sub b/tests/alias4.sub new file mode 100644 index 00000000..966b93d1 --- /dev/null +++ b/tests/alias4.sub @@ -0,0 +1,61 @@ +shopt -s expand_aliases + +# from an austin-group report +alias foo="echo 'Error:" +foo bar' + +# from some FreeBSD sh tests + +v=1 +alias a='unalias -a +v=2' +eval a +[ "$v" = 2 ] && echo ok 1 +v=1 +alias a='unalias a +v=2' +eval a +[ "$v" = 2 ] && echo ok 2 + +# make sure command doesn't ever reset anything even if it's made a keyword +unalias -a +alias command=command +alias true='echo bad' +eval 'command true' + +unalias -a +alias alias0=command +alias true='echo bad' +eval 'alias0 true' + +# make sure null aliases are ok +unalias -a +alias nullalias='' +alias foo='echo ' +foo nullalias text +unalias foo + +# aliases shouldn't be expanded in quoted strings even when the previous word +# is an alias whose expansion ends in a space +alias foo="echo 'whoops: " +foo nullalias' + +unalias -a + +# recursive alias definitions +alias echo=echo +eval echo foo + +alias echo='echo a' + +echo +echo b +eval echo b +echo $(eval echo b) + +unalias -a + +# alias expansion when in a command position after redirections +alias e=echo +eval '