|
@@ -5888,10 +5888,9 @@ static int substr_atoi(const char *s)
|
|
|
* performs globbing, and thus diverges from what we do).
|
|
|
*/
|
|
|
#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
|
|
|
-#define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */
|
|
|
-#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
|
|
|
-#define EXP_WORD 0x80 /* expand word in parameter expansion */
|
|
|
-#define EXP_QUOTED 0x100 /* expand word in double quotes */
|
|
|
+#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */
|
|
|
+#define EXP_WORD 0x40 /* expand word in parameter expansion */
|
|
|
+#define EXP_QUOTED 0x80 /* expand word in double quotes */
|
|
|
/*
|
|
|
* rmescape() flags
|
|
|
*/
|
|
@@ -5901,7 +5900,7 @@ static int substr_atoi(const char *s)
|
|
|
#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
|
|
|
|
|
|
/* Add CTLESC when necessary. */
|
|
|
-#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT)
|
|
|
+#define QUOTES_ESC (EXP_FULL | EXP_CASE)
|
|
|
/* Do not skip NUL characters. */
|
|
|
#define QUOTES_KEEPNUL EXP_TILDE
|
|
|
|
|
@@ -6090,7 +6089,6 @@ rmescapes(char *str, int flag, int *slash_position)
|
|
|
IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
|
|
|
|
|
|
char *p, *q, *r;
|
|
|
- unsigned inquotes;
|
|
|
unsigned protect_against_glob;
|
|
|
unsigned globbing;
|
|
|
|
|
@@ -6121,18 +6119,21 @@ rmescapes(char *str, int flag, int *slash_position)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- inquotes = 0;
|
|
|
globbing = flag & RMESCAPE_GLOB;
|
|
|
protect_against_glob = globbing;
|
|
|
while (*p) {
|
|
|
if ((unsigned char)*p == CTLQUOTEMARK) {
|
|
|
-// Note: both inquotes and protect_against_glob only affect whether
|
|
|
+// Note: protect_against_glob only affect whether
|
|
|
// CTLESC,<ch> gets converted to <ch> or to \<ch>
|
|
|
- inquotes = ~inquotes;
|
|
|
p++;
|
|
|
protect_against_glob = globbing;
|
|
|
continue;
|
|
|
}
|
|
|
+ if (*p == '\\') {
|
|
|
+ /* naked back slash */
|
|
|
+ protect_against_glob = 0;
|
|
|
+ goto copy;
|
|
|
+ }
|
|
|
if ((unsigned char)*p == CTLESC) {
|
|
|
p++;
|
|
|
#if DEBUG
|
|
@@ -6168,10 +6169,6 @@ rmescapes(char *str, int flag, int *slash_position)
|
|
|
*q++ = '\\';
|
|
|
}
|
|
|
}
|
|
|
- } else if (*p == '\\' && !inquotes) {
|
|
|
- /* naked back slash */
|
|
|
- protect_against_glob = 0;
|
|
|
- goto copy;
|
|
|
}
|
|
|
#if BASH_PATTERN_SUBST
|
|
|
else if (slash_position && p == str + *slash_position) {
|
|
@@ -6669,16 +6666,6 @@ argstr(char *p, int flags)
|
|
|
case CTLESC:
|
|
|
startloc++;
|
|
|
length++;
|
|
|
-
|
|
|
- /*
|
|
|
- * Quoted parameter expansion pattern: remove quote
|
|
|
- * unless inside inner quotes or we have a literal
|
|
|
- * backslash.
|
|
|
- */
|
|
|
- if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) ==
|
|
|
- EXP_QPAT && *p != '\\')
|
|
|
- break;
|
|
|
-
|
|
|
goto addquote;
|
|
|
case CTLVAR:
|
|
|
TRACE(("argstr: evalvar('%s')\n", p));
|
|
@@ -6869,15 +6856,24 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
|
|
|
}
|
|
|
#endif
|
|
|
argstr_flags = EXP_TILDE;
|
|
|
- if (subtype != VSASSIGN && subtype != VSQUESTION)
|
|
|
- argstr_flags |= (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE);
|
|
|
+ if (subtype != VSASSIGN
|
|
|
+ && subtype != VSQUESTION
|
|
|
+#if BASH_SUBSTR
|
|
|
+ && subtype != VSSUBSTR
|
|
|
+#endif
|
|
|
+ ) {
|
|
|
+ /* EXP_CASE keeps CTLESC's */
|
|
|
+ argstr_flags = EXP_TILDE | EXP_CASE;
|
|
|
+ }
|
|
|
argstr(p, argstr_flags);
|
|
|
+ //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc);
|
|
|
#if BASH_PATTERN_SUBST
|
|
|
slash_pos = -1;
|
|
|
if (repl) {
|
|
|
slash_pos = expdest - ((char *)stackblock() + strloc);
|
|
|
STPUTC('/', expdest);
|
|
|
- argstr(repl + 1, argstr_flags);
|
|
|
+ //bb_error_msg("repl+1:'%s'", repl + 1);
|
|
|
+ argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */
|
|
|
*repl = '/';
|
|
|
}
|
|
|
#endif
|
|
@@ -10669,6 +10665,34 @@ pgetc_eatbnl(void)
|
|
|
return c;
|
|
|
}
|
|
|
|
|
|
+struct synstack {
|
|
|
+ smalluint syntax;
|
|
|
+ uint8_t innerdq :1;
|
|
|
+ uint8_t varpushed :1;
|
|
|
+ uint8_t dblquote :1;
|
|
|
+ int varnest; /* levels of variables expansion */
|
|
|
+ int dqvarnest; /* levels of variables expansion within double quotes */
|
|
|
+ int parenlevel; /* levels of parens in arithmetic */
|
|
|
+ struct synstack *prev;
|
|
|
+ struct synstack *next;
|
|
|
+};
|
|
|
+
|
|
|
+static void
|
|
|
+synstack_push(struct synstack **stack, struct synstack *next, int syntax)
|
|
|
+{
|
|
|
+ memset(next, 0, sizeof(*next));
|
|
|
+ next->syntax = syntax;
|
|
|
+ next->next = *stack;
|
|
|
+ (*stack)->prev = next;
|
|
|
+ *stack = next;
|
|
|
+}
|
|
|
+
|
|
|
+static ALWAYS_INLINE void
|
|
|
+synstack_pop(struct synstack **stack)
|
|
|
+{
|
|
|
+ *stack = (*stack)->next;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* To handle the "." command, a stack of input files is used. Pushfile
|
|
|
* adds a new entry to the stack and popfile restores the previous level.
|
|
@@ -11928,19 +11952,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
size_t len;
|
|
|
struct nodelist *bqlist;
|
|
|
smallint quotef;
|
|
|
- smallint dblquote;
|
|
|
smallint oldstyle;
|
|
|
- IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
|
|
|
smallint pssyntax; /* we are expanding a prompt string */
|
|
|
- int varnest; /* levels of variables expansion */
|
|
|
- IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
|
|
|
- IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
|
|
|
- int dqvarnest; /* levels of variables expansion within double quotes */
|
|
|
IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
|
|
|
+ /* syntax stack */
|
|
|
+ struct synstack synbase = { .syntax = syntax };
|
|
|
+ struct synstack *synstack = &synbase;
|
|
|
|
|
|
- bqlist = NULL;
|
|
|
- quotef = 0;
|
|
|
- IF_FEATURE_SH_MATH(prevsyntax = 0;)
|
|
|
#if ENABLE_ASH_EXPAND_PRMT
|
|
|
pssyntax = (syntax == PSSYNTAX);
|
|
|
if (pssyntax)
|
|
@@ -11948,11 +11966,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
#else
|
|
|
pssyntax = 0; /* constant */
|
|
|
#endif
|
|
|
- dblquote = (syntax == DQSYNTAX);
|
|
|
- varnest = 0;
|
|
|
- IF_FEATURE_SH_MATH(arinest = 0;)
|
|
|
- IF_FEATURE_SH_MATH(parenlevel = 0;)
|
|
|
- dqvarnest = 0;
|
|
|
+ if (syntax == DQSYNTAX)
|
|
|
+ synstack->dblquote = 1;
|
|
|
+ quotef = 0;
|
|
|
+ bqlist = NULL;
|
|
|
|
|
|
STARTSTACKSTR(out);
|
|
|
loop:
|
|
@@ -11960,9 +11977,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
CHECKEND(); /* set c to PEOF if at end of here document */
|
|
|
for (;;) { /* until end of line or end of word */
|
|
|
CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
|
|
|
- switch (SIT(c, syntax)) {
|
|
|
+ switch (SIT(c, synstack->syntax)) {
|
|
|
case CNL: /* '\n' */
|
|
|
- if (syntax == BASESYNTAX)
|
|
|
+ if (synstack->syntax == BASESYNTAX)
|
|
|
goto endword; /* exit outer loop */
|
|
|
USTPUTC(c, out);
|
|
|
nlprompt();
|
|
@@ -11982,13 +11999,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
if (c & 0x100) {
|
|
|
/* Unknown escape. Encode as '\z' */
|
|
|
c = (unsigned char)c;
|
|
|
- if (eofmark == NULL || dblquote)
|
|
|
+ if (eofmark == NULL || synstack->dblquote)
|
|
|
USTPUTC(CTLESC, out);
|
|
|
USTPUTC('\\', out);
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
- if (eofmark == NULL || dblquote)
|
|
|
+ if (eofmark == NULL || synstack->dblquote)
|
|
|
USTPUTC(CTLESC, out);
|
|
|
USTPUTC(c, out);
|
|
|
break;
|
|
@@ -12008,20 +12025,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
/* Backslash is retained if we are in "str"
|
|
|
* and next char isn't dquote-special.
|
|
|
*/
|
|
|
- if (dblquote
|
|
|
+ if (synstack->dblquote
|
|
|
&& c != '\\'
|
|
|
&& c != '`'
|
|
|
&& c != '$'
|
|
|
- && (c != '"' || eofmark != NULL)
|
|
|
+ && (c != '"' || (eofmark != NULL && !synstack->varnest))
|
|
|
+ && (c != '}' || !synstack->varnest)
|
|
|
) {
|
|
|
-//dash survives not doing USTPUTC(CTLESC), but merely by chance:
|
|
|
-//Example: "\z" gets encoded as "\<CTLESC>z".
|
|
|
-//rmescapes() then emits "\", "\z", protecting z from globbing.
|
|
|
-//But it's wrong, should protect _both_ from globbing:
|
|
|
-//everything in double quotes is not globbed.
|
|
|
-//Unlike dash, we have a fix in rmescapes() which emits bare "z"
|
|
|
-//for "<CTLESC>z" since "z" is not glob-special (else unicode may break),
|
|
|
-//and glob would see "\z" and eat "\". Thus:
|
|
|
USTPUTC(CTLESC, out); /* protect '\' from glob */
|
|
|
USTPUTC('\\', out);
|
|
|
}
|
|
@@ -12031,56 +12041,62 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
}
|
|
|
break;
|
|
|
case CSQUOTE:
|
|
|
- syntax = SQSYNTAX;
|
|
|
+ synstack->syntax = SQSYNTAX;
|
|
|
quotemark:
|
|
|
if (eofmark == NULL) {
|
|
|
USTPUTC(CTLQUOTEMARK, out);
|
|
|
}
|
|
|
break;
|
|
|
case CDQUOTE:
|
|
|
- syntax = DQSYNTAX;
|
|
|
- dblquote = 1;
|
|
|
+ synstack->syntax = DQSYNTAX;
|
|
|
+ synstack->dblquote = 1;
|
|
|
+ toggledq:
|
|
|
+ if (synstack->varnest)
|
|
|
+ synstack->innerdq ^= 1;
|
|
|
goto quotemark;
|
|
|
case CENDQUOTE:
|
|
|
IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
|
|
|
- if (eofmark != NULL && varnest == 0) {
|
|
|
+ if (eofmark != NULL && synstack->varnest == 0) {
|
|
|
USTPUTC(c, out);
|
|
|
- } else {
|
|
|
- if (dqvarnest == 0) {
|
|
|
- syntax = BASESYNTAX;
|
|
|
- dblquote = 0;
|
|
|
- }
|
|
|
- quotef = 1;
|
|
|
- goto quotemark;
|
|
|
+ break;
|
|
|
}
|
|
|
- break;
|
|
|
+
|
|
|
+ if (synstack->dqvarnest == 0) {
|
|
|
+ synstack->syntax = BASESYNTAX;
|
|
|
+ synstack->dblquote = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ quotef = 1;
|
|
|
+
|
|
|
+ if (c == '"')
|
|
|
+ goto toggledq;
|
|
|
+
|
|
|
+ goto quotemark;
|
|
|
case CVAR: /* '$' */
|
|
|
PARSESUB(); /* parse substitution */
|
|
|
break;
|
|
|
case CENDVAR: /* '}' */
|
|
|
- if (varnest > 0) {
|
|
|
- varnest--;
|
|
|
- if (dqvarnest > 0) {
|
|
|
- dqvarnest--;
|
|
|
- }
|
|
|
+ if (!synstack->innerdq && synstack->varnest > 0) {
|
|
|
+ if (!--synstack->varnest && synstack->varpushed)
|
|
|
+ synstack_pop(&synstack);
|
|
|
+ else if (synstack->dqvarnest > 0)
|
|
|
+ synstack->dqvarnest--;
|
|
|
c = CTLENDVAR;
|
|
|
}
|
|
|
USTPUTC(c, out);
|
|
|
break;
|
|
|
#if ENABLE_FEATURE_SH_MATH
|
|
|
case CLP: /* '(' in arithmetic */
|
|
|
- parenlevel++;
|
|
|
+ synstack->parenlevel++;
|
|
|
USTPUTC(c, out);
|
|
|
break;
|
|
|
case CRP: /* ')' in arithmetic */
|
|
|
- if (parenlevel > 0) {
|
|
|
- parenlevel--;
|
|
|
+ if (synstack->parenlevel > 0) {
|
|
|
+ synstack->parenlevel--;
|
|
|
} else {
|
|
|
if (pgetc_eatbnl() == ')') {
|
|
|
c = CTLENDARI;
|
|
|
- if (--arinest == 0) {
|
|
|
- syntax = prevsyntax;
|
|
|
- }
|
|
|
+ synstack_pop(&synstack);
|
|
|
} else {
|
|
|
/*
|
|
|
* unbalanced parens
|
|
@@ -12106,7 +12122,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
case CIGN:
|
|
|
break;
|
|
|
default:
|
|
|
- if (varnest == 0) {
|
|
|
+ if (synstack->varnest == 0) {
|
|
|
#if BASH_REDIR_OUTPUT
|
|
|
if (c == '&') {
|
|
|
//Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
|
|
@@ -12125,12 +12141,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
|
|
|
endword:
|
|
|
|
|
|
#if ENABLE_FEATURE_SH_MATH
|
|
|
- if (syntax == ARISYNTAX)
|
|
|
+ if (synstack->syntax == ARISYNTAX)
|
|
|
raise_error_syntax("missing '))'");
|
|
|
#endif
|
|
|
- if (syntax != BASESYNTAX && eofmark == NULL)
|
|
|
+ if (synstack->syntax != BASESYNTAX && eofmark == NULL)
|
|
|
raise_error_syntax("unterminated quoted string");
|
|
|
- if (varnest != 0) {
|
|
|
+ if (synstack->varnest != 0) {
|
|
|
/* { */
|
|
|
raise_error_syntax("missing '}'");
|
|
|
}
|
|
@@ -12312,7 +12328,7 @@ parsesub: {
|
|
|
|| (c != '(' && c != '{' && !is_name(c) && !is_special(c))
|
|
|
) {
|
|
|
#if BASH_DOLLAR_SQUOTE
|
|
|
- if (syntax != DQSYNTAX && c == '\'')
|
|
|
+ if (synstack->syntax != DQSYNTAX && c == '\'')
|
|
|
bash_dollar_squote = 1;
|
|
|
else
|
|
|
#endif
|
|
@@ -12332,6 +12348,8 @@ parsesub: {
|
|
|
}
|
|
|
} else {
|
|
|
/* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
|
|
|
+ smalluint newsyn = synstack->syntax;
|
|
|
+
|
|
|
USTPUTC(CTLVAR, out);
|
|
|
typeloc = out - (char *)stackblock();
|
|
|
STADJUST(1, out);
|
|
@@ -12390,6 +12408,8 @@ parsesub: {
|
|
|
static const char types[] ALIGN1 = "}-+?=";
|
|
|
/* ${VAR...} but not $VAR or ${#VAR} */
|
|
|
/* c == first char after VAR */
|
|
|
+ int cc = c;
|
|
|
+
|
|
|
switch (c) {
|
|
|
case ':':
|
|
|
c = pgetc_eatbnl();
|
|
@@ -12414,21 +12434,24 @@ parsesub: {
|
|
|
break;
|
|
|
}
|
|
|
case '%':
|
|
|
- case '#': {
|
|
|
- int cc = c;
|
|
|
+ case '#':
|
|
|
subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
|
|
|
c = pgetc_eatbnl();
|
|
|
- if (c != cc)
|
|
|
- goto badsub;
|
|
|
- subtype++;
|
|
|
+ if (c == cc)
|
|
|
+ subtype++;
|
|
|
+ else
|
|
|
+ pungetc();
|
|
|
+
|
|
|
+ newsyn = BASESYNTAX;
|
|
|
break;
|
|
|
- }
|
|
|
#if BASH_PATTERN_SUBST
|
|
|
case '/':
|
|
|
/* ${v/[/]pattern/repl} */
|
|
|
//TODO: encode pattern and repl separately.
|
|
|
-// Currently ${v/$var_with_slash/repl} is horribly broken
|
|
|
+// Currently cases like: v=1;echo ${v/$((1/1))/ONE}
|
|
|
+// are broken (should print "ONE")
|
|
|
subtype = VSREPLACE;
|
|
|
+ newsyn = BASESYNTAX;
|
|
|
c = pgetc_eatbnl();
|
|
|
if (c != '/')
|
|
|
goto badsub;
|
|
@@ -12440,11 +12463,24 @@ parsesub: {
|
|
|
badsub:
|
|
|
pungetc();
|
|
|
}
|
|
|
+
|
|
|
+ if (newsyn == ARISYNTAX && subtype > VSNORMAL)
|
|
|
+ newsyn = DQSYNTAX;
|
|
|
+
|
|
|
+ if (newsyn != synstack->syntax) {
|
|
|
+ synstack_push(&synstack,
|
|
|
+ synstack->prev ?: alloca(sizeof(*synstack)),
|
|
|
+ newsyn);
|
|
|
+
|
|
|
+ synstack->varpushed = 1;
|
|
|
+ synstack->dblquote = newsyn != BASESYNTAX;
|
|
|
+ }
|
|
|
+
|
|
|
((unsigned char *)stackblock())[typeloc] = subtype;
|
|
|
if (subtype != VSNORMAL) {
|
|
|
- varnest++;
|
|
|
- if (dblquote)
|
|
|
- dqvarnest++;
|
|
|
+ synstack->varnest++;
|
|
|
+ if (synstack->dblquote)
|
|
|
+ synstack->dqvarnest++;
|
|
|
}
|
|
|
STPUTC('=', out);
|
|
|
}
|
|
@@ -12501,7 +12537,7 @@ parsebackq: {
|
|
|
case '\\':
|
|
|
pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */
|
|
|
if (pc != '\\' && pc != '`' && pc != '$'
|
|
|
- && (!dblquote || pc != '"')
|
|
|
+ && (!synstack->dblquote || pc != '"')
|
|
|
) {
|
|
|
STPUTC('\\', pout);
|
|
|
}
|
|
@@ -12576,10 +12612,11 @@ parsebackq: {
|
|
|
* Parse an arithmetic expansion (indicate start of one and set state)
|
|
|
*/
|
|
|
parsearith: {
|
|
|
- if (++arinest == 1) {
|
|
|
- prevsyntax = syntax;
|
|
|
- syntax = ARISYNTAX;
|
|
|
- }
|
|
|
+
|
|
|
+ synstack_push(&synstack,
|
|
|
+ synstack->prev ?: alloca(sizeof(*synstack)),
|
|
|
+ ARISYNTAX);
|
|
|
+ synstack->dblquote = 1;
|
|
|
USTPUTC(CTLARI, out);
|
|
|
goto parsearith_return;
|
|
|
}
|