ash: expand: Fix ghost fields with unquoted $@/$*

Upstream commit:

    Date: Fri, 23 Mar 2018 18:58:47 +0800
    expand: Fix ghost fields with unquoted $@/$*

    You're right.  The proper fix to this is to ensure that nulonly
    is not set in varvalue for $*.  It should only be set for $@ when
    it's inside double quotes.

    In fact there is another bug while we're playing with $@/$*.
    When IFS is set to a non-whitespace character such as :, $*
    outside quotes won't remove empty fields as it should.

    This patch fixes both problems.

    Reported-by: Martijn Dekker <martijn@inlv.org>
    Suggested-by: Harald van Dijk <harald@gigawatt.nl>
    Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

function                                             old     new   delta
argstr                                              1111    1113      +2
evalvar                                              571     569      -2
varvalue                                             579     576      -3
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/2 up/down: 2/-5)               Total: -3 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2018-08-05 14:29:58 +02:00
parent 67dae152f4
commit 440da97ed7
5 changed files with 36 additions and 12 deletions

View File

@ -5902,7 +5902,7 @@ static int substr_atoi(const char *s)
#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ #define EXP_VARTILDE2 0x20 /* expand tildes after colons only */
#define EXP_WORD 0x40 /* expand word in parameter expansion */ #define EXP_WORD 0x40 /* expand word in parameter expansion */
#define EXP_QUOTED 0x80 /* expand word in double quotes */ #define EXP_QUOTED 0x100 /* expand word in double quotes */
/* /*
* rmescape() flags * rmescape() flags
*/ */
@ -7175,14 +7175,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
* ash -c 'echo ${#1#}' name:'1=#' * ash -c 'echo ${#1#}' name:'1=#'
*/ */
static NOINLINE ssize_t static NOINLINE ssize_t
varvalue(char *name, int varflags, int flags, int *quotedp) varvalue(char *name, int varflags, int flags, int quoted)
{ {
const char *p; const char *p;
int num; int num;
int i; int i;
ssize_t len = 0; ssize_t len = 0;
int sep; int sep;
int quoted = *quotedp;
int subtype = varflags & VSTYPE; int subtype = varflags & VSTYPE;
int discard = subtype == VSPLUS || subtype == VSLENGTH; int discard = subtype == VSPLUS || subtype == VSLENGTH;
int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
@ -7230,13 +7229,27 @@ varvalue(char *name, int varflags, int flags, int *quotedp)
case '*': { case '*': {
char **ap; char **ap;
char sepc; char sepc;
char c;
if (quoted) /* We will set c to 0 or ~0 depending on whether
sep = 0; * we're doing field splitting. We won't do field
sep |= ifsset() ? ifsval()[0] : ' '; * splitting if either we're quoted or sep is zero.
*
* Instead of testing (quoted || !sep) the following
* trick optimises away any branches by using the
* fact that EXP_QUOTED (which is the only bit that
* can be set in quoted) is the same as EXP_FULL <<
* CHAR_BIT (which is the only bit that can be set
* in sep).
*/
#if EXP_QUOTED >> CHAR_BIT != EXP_FULL
#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT
#endif
c = !((quoted | ~sep) & EXP_QUOTED) - 1;
sep &= ~quoted;
sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
param: param:
sepc = sep; sepc = sep;
*quotedp = !sepc;
ap = shellparam.p; ap = shellparam.p;
if (!ap) if (!ap)
return -1; return -1;
@ -7301,7 +7314,6 @@ evalvar(char *p, int flag)
char varflags; char varflags;
char subtype; char subtype;
int quoted; int quoted;
char easy;
char *var; char *var;
int patloc; int patloc;
int startloc; int startloc;
@ -7315,12 +7327,11 @@ evalvar(char *p, int flag)
quoted = flag & EXP_QUOTED; quoted = flag & EXP_QUOTED;
var = p; var = p;
easy = (!quoted || (*var == '@' && shellparam.nparam));
startloc = expdest - (char *)stackblock(); startloc = expdest - (char *)stackblock();
p = strchr(p, '=') + 1; //TODO: use var_end(p)? p = strchr(p, '=') + 1; //TODO: use var_end(p)?
again: again:
varlen = varvalue(var, varflags, flag, &quoted); varlen = varvalue(var, varflags, flag, quoted);
if (varflags & VSNUL) if (varflags & VSNUL)
varlen--; varlen--;
@ -7366,8 +7377,11 @@ evalvar(char *p, int flag)
if (subtype == VSNORMAL) { if (subtype == VSNORMAL) {
record: record:
if (!easy) if (quoted) {
goto end; quoted = *var == '@' && shellparam.nparam;
if (!quoted)
goto end;
}
recordregion(startloc, expdest - (char *)stackblock(), quoted); recordregion(startloc, expdest - (char *)stackblock(), quoted);
goto end; goto end;
} }

View File

@ -0,0 +1 @@
Zero:0

View File

@ -0,0 +1,4 @@
IFS=
set --
set -- $@ $*
echo Zero:$#

View File

@ -0,0 +1 @@
Zero:0

View File

@ -0,0 +1,4 @@
IFS=
set --
set -- $@ $*
echo Zero:$#