diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2018-04-02 12:35:04 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2018-04-02 13:15:37 +0200 |
commit | 216913c290fd2b88b744c04c0a2ef21fd1410ba9 (patch) | |
tree | 906139aa9c6d05a091c55ab83d1bf547fe839f31 | |
parent | e84212f8346741a2d4a04b40639c44fe519cf5a7 (diff) | |
download | busybox-w32-216913c290fd2b88b744c04c0a2ef21fd1410ba9.tar.gz busybox-w32-216913c290fd2b88b744c04c0a2ef21fd1410ba9.tar.bz2 busybox-w32-216913c290fd2b88b744c04c0a2ef21fd1410ba9.zip |
ash: parser: Add syntax stack for recursive parsing
This closes 10821.
Upstream patch:
From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Fri, 9 Mar 2018 00:14:02 +0800
parser: Add syntax stack for recursive parsing
Without a stack of syntaxes we cannot correctly these two cases
together:
"${a#'$$'}"
"${a#"${b-'$$'}"}"
A recursive parser also helps in some other corner cases such
as nested arithmetic expansion with paratheses.
This patch adds a syntax stack allocated from the stack using
alloca. As a side-effect this allows us to remove the naked
backslashes for patterns within double-quotes, which means that
EXP_QPAT also has to go.
This patch also fixes removes any backslashes that precede right
braces when they are present within a parameter expansion context,
and backslashes that precede double quotes within inner double
quotes inside a parameter expansion in a here-document context.
The idea of a recursive parser is based on a patch by Harald van
Dijk.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
var_bash3, var_bash4 and var_bash6 tests are updated
with the output given by bash-4.3.43
With this patch, the following tests now pass for ash:
dollar_repl_slash_bash2.tests
squote_in_varexp2.tests
squote_in_varexp.tests
var_bash4.tests
function old new delta
readtoken1 2615 2874 +259
synstack_push - 54 +54
evalvar 574 571 -3
rmescapes 330 310 -20
subevalvar 1279 1258 -21
argstr 1146 1107 -39
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 1/4 up/down: 313/-83) Total: 230 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
19 files changed, 168 insertions, 121 deletions
diff --git a/shell/ash.c b/shell/ash.c index cf1d062fb..97379cd92 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -5888,10 +5888,9 @@ static int substr_atoi(const char *s) | |||
5888 | * performs globbing, and thus diverges from what we do). | 5888 | * performs globbing, and thus diverges from what we do). |
5889 | */ | 5889 | */ |
5890 | #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ | 5890 | #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ |
5891 | #define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */ | 5891 | #define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ |
5892 | #define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ | 5892 | #define EXP_WORD 0x40 /* expand word in parameter expansion */ |
5893 | #define EXP_WORD 0x80 /* expand word in parameter expansion */ | 5893 | #define EXP_QUOTED 0x80 /* expand word in double quotes */ |
5894 | #define EXP_QUOTED 0x100 /* expand word in double quotes */ | ||
5895 | /* | 5894 | /* |
5896 | * rmescape() flags | 5895 | * rmescape() flags |
5897 | */ | 5896 | */ |
@@ -5901,7 +5900,7 @@ static int substr_atoi(const char *s) | |||
5901 | #define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ | 5900 | #define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ |
5902 | 5901 | ||
5903 | /* Add CTLESC when necessary. */ | 5902 | /* Add CTLESC when necessary. */ |
5904 | #define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) | 5903 | #define QUOTES_ESC (EXP_FULL | EXP_CASE) |
5905 | /* Do not skip NUL characters. */ | 5904 | /* Do not skip NUL characters. */ |
5906 | #define QUOTES_KEEPNUL EXP_TILDE | 5905 | #define QUOTES_KEEPNUL EXP_TILDE |
5907 | 5906 | ||
@@ -6090,7 +6089,6 @@ rmescapes(char *str, int flag, int *slash_position) | |||
6090 | IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' }; | 6089 | IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' }; |
6091 | 6090 | ||
6092 | char *p, *q, *r; | 6091 | char *p, *q, *r; |
6093 | unsigned inquotes; | ||
6094 | unsigned protect_against_glob; | 6092 | unsigned protect_against_glob; |
6095 | unsigned globbing; | 6093 | unsigned globbing; |
6096 | 6094 | ||
@@ -6121,18 +6119,21 @@ rmescapes(char *str, int flag, int *slash_position) | |||
6121 | } | 6119 | } |
6122 | } | 6120 | } |
6123 | 6121 | ||
6124 | inquotes = 0; | ||
6125 | globbing = flag & RMESCAPE_GLOB; | 6122 | globbing = flag & RMESCAPE_GLOB; |
6126 | protect_against_glob = globbing; | 6123 | protect_against_glob = globbing; |
6127 | while (*p) { | 6124 | while (*p) { |
6128 | if ((unsigned char)*p == CTLQUOTEMARK) { | 6125 | if ((unsigned char)*p == CTLQUOTEMARK) { |
6129 | // Note: both inquotes and protect_against_glob only affect whether | 6126 | // Note: protect_against_glob only affect whether |
6130 | // CTLESC,<ch> gets converted to <ch> or to \<ch> | 6127 | // CTLESC,<ch> gets converted to <ch> or to \<ch> |
6131 | inquotes = ~inquotes; | ||
6132 | p++; | 6128 | p++; |
6133 | protect_against_glob = globbing; | 6129 | protect_against_glob = globbing; |
6134 | continue; | 6130 | continue; |
6135 | } | 6131 | } |
6132 | if (*p == '\\') { | ||
6133 | /* naked back slash */ | ||
6134 | protect_against_glob = 0; | ||
6135 | goto copy; | ||
6136 | } | ||
6136 | if ((unsigned char)*p == CTLESC) { | 6137 | if ((unsigned char)*p == CTLESC) { |
6137 | p++; | 6138 | p++; |
6138 | #if DEBUG | 6139 | #if DEBUG |
@@ -6168,10 +6169,6 @@ rmescapes(char *str, int flag, int *slash_position) | |||
6168 | *q++ = '\\'; | 6169 | *q++ = '\\'; |
6169 | } | 6170 | } |
6170 | } | 6171 | } |
6171 | } else if (*p == '\\' && !inquotes) { | ||
6172 | /* naked back slash */ | ||
6173 | protect_against_glob = 0; | ||
6174 | goto copy; | ||
6175 | } | 6172 | } |
6176 | #if BASH_PATTERN_SUBST | 6173 | #if BASH_PATTERN_SUBST |
6177 | else if (slash_position && p == str + *slash_position) { | 6174 | else if (slash_position && p == str + *slash_position) { |
@@ -6669,16 +6666,6 @@ argstr(char *p, int flags) | |||
6669 | case CTLESC: | 6666 | case CTLESC: |
6670 | startloc++; | 6667 | startloc++; |
6671 | length++; | 6668 | length++; |
6672 | |||
6673 | /* | ||
6674 | * Quoted parameter expansion pattern: remove quote | ||
6675 | * unless inside inner quotes or we have a literal | ||
6676 | * backslash. | ||
6677 | */ | ||
6678 | if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) == | ||
6679 | EXP_QPAT && *p != '\\') | ||
6680 | break; | ||
6681 | |||
6682 | goto addquote; | 6669 | goto addquote; |
6683 | case CTLVAR: | 6670 | case CTLVAR: |
6684 | TRACE(("argstr: evalvar('%s')\n", p)); | 6671 | TRACE(("argstr: evalvar('%s')\n", p)); |
@@ -6869,15 +6856,24 @@ subevalvar(char *p, char *varname, int strloc, int subtype, | |||
6869 | } | 6856 | } |
6870 | #endif | 6857 | #endif |
6871 | argstr_flags = EXP_TILDE; | 6858 | argstr_flags = EXP_TILDE; |
6872 | if (subtype != VSASSIGN && subtype != VSQUESTION) | 6859 | if (subtype != VSASSIGN |
6873 | argstr_flags |= (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE); | 6860 | && subtype != VSQUESTION |
6861 | #if BASH_SUBSTR | ||
6862 | && subtype != VSSUBSTR | ||
6863 | #endif | ||
6864 | ) { | ||
6865 | /* EXP_CASE keeps CTLESC's */ | ||
6866 | argstr_flags = EXP_TILDE | EXP_CASE; | ||
6867 | } | ||
6874 | argstr(p, argstr_flags); | 6868 | argstr(p, argstr_flags); |
6869 | //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc); | ||
6875 | #if BASH_PATTERN_SUBST | 6870 | #if BASH_PATTERN_SUBST |
6876 | slash_pos = -1; | 6871 | slash_pos = -1; |
6877 | if (repl) { | 6872 | if (repl) { |
6878 | slash_pos = expdest - ((char *)stackblock() + strloc); | 6873 | slash_pos = expdest - ((char *)stackblock() + strloc); |
6879 | STPUTC('/', expdest); | 6874 | STPUTC('/', expdest); |
6880 | argstr(repl + 1, argstr_flags); | 6875 | //bb_error_msg("repl+1:'%s'", repl + 1); |
6876 | argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */ | ||
6881 | *repl = '/'; | 6877 | *repl = '/'; |
6882 | } | 6878 | } |
6883 | #endif | 6879 | #endif |
@@ -10669,6 +10665,34 @@ pgetc_eatbnl(void) | |||
10669 | return c; | 10665 | return c; |
10670 | } | 10666 | } |
10671 | 10667 | ||
10668 | struct synstack { | ||
10669 | smalluint syntax; | ||
10670 | uint8_t innerdq :1; | ||
10671 | uint8_t varpushed :1; | ||
10672 | uint8_t dblquote :1; | ||
10673 | int varnest; /* levels of variables expansion */ | ||
10674 | int dqvarnest; /* levels of variables expansion within double quotes */ | ||
10675 | int parenlevel; /* levels of parens in arithmetic */ | ||
10676 | struct synstack *prev; | ||
10677 | struct synstack *next; | ||
10678 | }; | ||
10679 | |||
10680 | static void | ||
10681 | synstack_push(struct synstack **stack, struct synstack *next, int syntax) | ||
10682 | { | ||
10683 | memset(next, 0, sizeof(*next)); | ||
10684 | next->syntax = syntax; | ||
10685 | next->next = *stack; | ||
10686 | (*stack)->prev = next; | ||
10687 | *stack = next; | ||
10688 | } | ||
10689 | |||
10690 | static ALWAYS_INLINE void | ||
10691 | synstack_pop(struct synstack **stack) | ||
10692 | { | ||
10693 | *stack = (*stack)->next; | ||
10694 | } | ||
10695 | |||
10672 | /* | 10696 | /* |
10673 | * To handle the "." command, a stack of input files is used. Pushfile | 10697 | * To handle the "." command, a stack of input files is used. Pushfile |
10674 | * adds a new entry to the stack and popfile restores the previous level. | 10698 | * 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) | |||
11928 | size_t len; | 11952 | size_t len; |
11929 | struct nodelist *bqlist; | 11953 | struct nodelist *bqlist; |
11930 | smallint quotef; | 11954 | smallint quotef; |
11931 | smallint dblquote; | ||
11932 | smallint oldstyle; | 11955 | smallint oldstyle; |
11933 | IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */ | ||
11934 | smallint pssyntax; /* we are expanding a prompt string */ | 11956 | smallint pssyntax; /* we are expanding a prompt string */ |
11935 | int varnest; /* levels of variables expansion */ | ||
11936 | IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ | ||
11937 | IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ | ||
11938 | int dqvarnest; /* levels of variables expansion within double quotes */ | ||
11939 | IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) | 11957 | IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) |
11958 | /* syntax stack */ | ||
11959 | struct synstack synbase = { .syntax = syntax }; | ||
11960 | struct synstack *synstack = &synbase; | ||
11940 | 11961 | ||
11941 | bqlist = NULL; | ||
11942 | quotef = 0; | ||
11943 | IF_FEATURE_SH_MATH(prevsyntax = 0;) | ||
11944 | #if ENABLE_ASH_EXPAND_PRMT | 11962 | #if ENABLE_ASH_EXPAND_PRMT |
11945 | pssyntax = (syntax == PSSYNTAX); | 11963 | pssyntax = (syntax == PSSYNTAX); |
11946 | if (pssyntax) | 11964 | if (pssyntax) |
@@ -11948,11 +11966,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
11948 | #else | 11966 | #else |
11949 | pssyntax = 0; /* constant */ | 11967 | pssyntax = 0; /* constant */ |
11950 | #endif | 11968 | #endif |
11951 | dblquote = (syntax == DQSYNTAX); | 11969 | if (syntax == DQSYNTAX) |
11952 | varnest = 0; | 11970 | synstack->dblquote = 1; |
11953 | IF_FEATURE_SH_MATH(arinest = 0;) | 11971 | quotef = 0; |
11954 | IF_FEATURE_SH_MATH(parenlevel = 0;) | 11972 | bqlist = NULL; |
11955 | dqvarnest = 0; | ||
11956 | 11973 | ||
11957 | STARTSTACKSTR(out); | 11974 | STARTSTACKSTR(out); |
11958 | loop: | 11975 | loop: |
@@ -11960,9 +11977,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
11960 | CHECKEND(); /* set c to PEOF if at end of here document */ | 11977 | CHECKEND(); /* set c to PEOF if at end of here document */ |
11961 | for (;;) { /* until end of line or end of word */ | 11978 | for (;;) { /* until end of line or end of word */ |
11962 | CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ | 11979 | CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ |
11963 | switch (SIT(c, syntax)) { | 11980 | switch (SIT(c, synstack->syntax)) { |
11964 | case CNL: /* '\n' */ | 11981 | case CNL: /* '\n' */ |
11965 | if (syntax == BASESYNTAX) | 11982 | if (synstack->syntax == BASESYNTAX) |
11966 | goto endword; /* exit outer loop */ | 11983 | goto endword; /* exit outer loop */ |
11967 | USTPUTC(c, out); | 11984 | USTPUTC(c, out); |
11968 | nlprompt(); | 11985 | nlprompt(); |
@@ -11982,13 +11999,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
11982 | if (c & 0x100) { | 11999 | if (c & 0x100) { |
11983 | /* Unknown escape. Encode as '\z' */ | 12000 | /* Unknown escape. Encode as '\z' */ |
11984 | c = (unsigned char)c; | 12001 | c = (unsigned char)c; |
11985 | if (eofmark == NULL || dblquote) | 12002 | if (eofmark == NULL || synstack->dblquote) |
11986 | USTPUTC(CTLESC, out); | 12003 | USTPUTC(CTLESC, out); |
11987 | USTPUTC('\\', out); | 12004 | USTPUTC('\\', out); |
11988 | } | 12005 | } |
11989 | } | 12006 | } |
11990 | #endif | 12007 | #endif |
11991 | if (eofmark == NULL || dblquote) | 12008 | if (eofmark == NULL || synstack->dblquote) |
11992 | USTPUTC(CTLESC, out); | 12009 | USTPUTC(CTLESC, out); |
11993 | USTPUTC(c, out); | 12010 | USTPUTC(c, out); |
11994 | break; | 12011 | break; |
@@ -12008,20 +12025,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
12008 | /* Backslash is retained if we are in "str" | 12025 | /* Backslash is retained if we are in "str" |
12009 | * and next char isn't dquote-special. | 12026 | * and next char isn't dquote-special. |
12010 | */ | 12027 | */ |
12011 | if (dblquote | 12028 | if (synstack->dblquote |
12012 | && c != '\\' | 12029 | && c != '\\' |
12013 | && c != '`' | 12030 | && c != '`' |
12014 | && c != '$' | 12031 | && c != '$' |
12015 | && (c != '"' || eofmark != NULL) | 12032 | && (c != '"' || (eofmark != NULL && !synstack->varnest)) |
12033 | && (c != '}' || !synstack->varnest) | ||
12016 | ) { | 12034 | ) { |
12017 | //dash survives not doing USTPUTC(CTLESC), but merely by chance: | ||
12018 | //Example: "\z" gets encoded as "\<CTLESC>z". | ||
12019 | //rmescapes() then emits "\", "\z", protecting z from globbing. | ||
12020 | //But it's wrong, should protect _both_ from globbing: | ||
12021 | //everything in double quotes is not globbed. | ||
12022 | //Unlike dash, we have a fix in rmescapes() which emits bare "z" | ||
12023 | //for "<CTLESC>z" since "z" is not glob-special (else unicode may break), | ||
12024 | //and glob would see "\z" and eat "\". Thus: | ||
12025 | USTPUTC(CTLESC, out); /* protect '\' from glob */ | 12035 | USTPUTC(CTLESC, out); /* protect '\' from glob */ |
12026 | USTPUTC('\\', out); | 12036 | USTPUTC('\\', out); |
12027 | } | 12037 | } |
@@ -12031,56 +12041,62 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
12031 | } | 12041 | } |
12032 | break; | 12042 | break; |
12033 | case CSQUOTE: | 12043 | case CSQUOTE: |
12034 | syntax = SQSYNTAX; | 12044 | synstack->syntax = SQSYNTAX; |
12035 | quotemark: | 12045 | quotemark: |
12036 | if (eofmark == NULL) { | 12046 | if (eofmark == NULL) { |
12037 | USTPUTC(CTLQUOTEMARK, out); | 12047 | USTPUTC(CTLQUOTEMARK, out); |
12038 | } | 12048 | } |
12039 | break; | 12049 | break; |
12040 | case CDQUOTE: | 12050 | case CDQUOTE: |
12041 | syntax = DQSYNTAX; | 12051 | synstack->syntax = DQSYNTAX; |
12042 | dblquote = 1; | 12052 | synstack->dblquote = 1; |
12053 | toggledq: | ||
12054 | if (synstack->varnest) | ||
12055 | synstack->innerdq ^= 1; | ||
12043 | goto quotemark; | 12056 | goto quotemark; |
12044 | case CENDQUOTE: | 12057 | case CENDQUOTE: |
12045 | IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;) | 12058 | IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;) |
12046 | if (eofmark != NULL && varnest == 0) { | 12059 | if (eofmark != NULL && synstack->varnest == 0) { |
12047 | USTPUTC(c, out); | 12060 | USTPUTC(c, out); |
12048 | } else { | 12061 | break; |
12049 | if (dqvarnest == 0) { | ||
12050 | syntax = BASESYNTAX; | ||
12051 | dblquote = 0; | ||
12052 | } | ||
12053 | quotef = 1; | ||
12054 | goto quotemark; | ||
12055 | } | 12062 | } |
12056 | break; | 12063 | |
12064 | if (synstack->dqvarnest == 0) { | ||
12065 | synstack->syntax = BASESYNTAX; | ||
12066 | synstack->dblquote = 0; | ||
12067 | } | ||
12068 | |||
12069 | quotef = 1; | ||
12070 | |||
12071 | if (c == '"') | ||
12072 | goto toggledq; | ||
12073 | |||
12074 | goto quotemark; | ||
12057 | case CVAR: /* '$' */ | 12075 | case CVAR: /* '$' */ |
12058 | PARSESUB(); /* parse substitution */ | 12076 | PARSESUB(); /* parse substitution */ |
12059 | break; | 12077 | break; |
12060 | case CENDVAR: /* '}' */ | 12078 | case CENDVAR: /* '}' */ |
12061 | if (varnest > 0) { | 12079 | if (!synstack->innerdq && synstack->varnest > 0) { |
12062 | varnest--; | 12080 | if (!--synstack->varnest && synstack->varpushed) |
12063 | if (dqvarnest > 0) { | 12081 | synstack_pop(&synstack); |
12064 | dqvarnest--; | 12082 | else if (synstack->dqvarnest > 0) |
12065 | } | 12083 | synstack->dqvarnest--; |
12066 | c = CTLENDVAR; | 12084 | c = CTLENDVAR; |
12067 | } | 12085 | } |
12068 | USTPUTC(c, out); | 12086 | USTPUTC(c, out); |
12069 | break; | 12087 | break; |
12070 | #if ENABLE_FEATURE_SH_MATH | 12088 | #if ENABLE_FEATURE_SH_MATH |
12071 | case CLP: /* '(' in arithmetic */ | 12089 | case CLP: /* '(' in arithmetic */ |
12072 | parenlevel++; | 12090 | synstack->parenlevel++; |
12073 | USTPUTC(c, out); | 12091 | USTPUTC(c, out); |
12074 | break; | 12092 | break; |
12075 | case CRP: /* ')' in arithmetic */ | 12093 | case CRP: /* ')' in arithmetic */ |
12076 | if (parenlevel > 0) { | 12094 | if (synstack->parenlevel > 0) { |
12077 | parenlevel--; | 12095 | synstack->parenlevel--; |
12078 | } else { | 12096 | } else { |
12079 | if (pgetc_eatbnl() == ')') { | 12097 | if (pgetc_eatbnl() == ')') { |
12080 | c = CTLENDARI; | 12098 | c = CTLENDARI; |
12081 | if (--arinest == 0) { | 12099 | synstack_pop(&synstack); |
12082 | syntax = prevsyntax; | ||
12083 | } | ||
12084 | } else { | 12100 | } else { |
12085 | /* | 12101 | /* |
12086 | * unbalanced parens | 12102 | * unbalanced parens |
@@ -12106,7 +12122,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
12106 | case CIGN: | 12122 | case CIGN: |
12107 | break; | 12123 | break; |
12108 | default: | 12124 | default: |
12109 | if (varnest == 0) { | 12125 | if (synstack->varnest == 0) { |
12110 | #if BASH_REDIR_OUTPUT | 12126 | #if BASH_REDIR_OUTPUT |
12111 | if (c == '&') { | 12127 | if (c == '&') { |
12112 | //Can't call pgetc_eatbnl() here, this requires three-deep pungetc() | 12128 | //Can't call pgetc_eatbnl() here, this requires three-deep pungetc() |
@@ -12125,12 +12141,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
12125 | endword: | 12141 | endword: |
12126 | 12142 | ||
12127 | #if ENABLE_FEATURE_SH_MATH | 12143 | #if ENABLE_FEATURE_SH_MATH |
12128 | if (syntax == ARISYNTAX) | 12144 | if (synstack->syntax == ARISYNTAX) |
12129 | raise_error_syntax("missing '))'"); | 12145 | raise_error_syntax("missing '))'"); |
12130 | #endif | 12146 | #endif |
12131 | if (syntax != BASESYNTAX && eofmark == NULL) | 12147 | if (synstack->syntax != BASESYNTAX && eofmark == NULL) |
12132 | raise_error_syntax("unterminated quoted string"); | 12148 | raise_error_syntax("unterminated quoted string"); |
12133 | if (varnest != 0) { | 12149 | if (synstack->varnest != 0) { |
12134 | /* { */ | 12150 | /* { */ |
12135 | raise_error_syntax("missing '}'"); | 12151 | raise_error_syntax("missing '}'"); |
12136 | } | 12152 | } |
@@ -12312,7 +12328,7 @@ parsesub: { | |||
12312 | || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) | 12328 | || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) |
12313 | ) { | 12329 | ) { |
12314 | #if BASH_DOLLAR_SQUOTE | 12330 | #if BASH_DOLLAR_SQUOTE |
12315 | if (syntax != DQSYNTAX && c == '\'') | 12331 | if (synstack->syntax != DQSYNTAX && c == '\'') |
12316 | bash_dollar_squote = 1; | 12332 | bash_dollar_squote = 1; |
12317 | else | 12333 | else |
12318 | #endif | 12334 | #endif |
@@ -12332,6 +12348,8 @@ parsesub: { | |||
12332 | } | 12348 | } |
12333 | } else { | 12349 | } else { |
12334 | /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */ | 12350 | /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */ |
12351 | smalluint newsyn = synstack->syntax; | ||
12352 | |||
12335 | USTPUTC(CTLVAR, out); | 12353 | USTPUTC(CTLVAR, out); |
12336 | typeloc = out - (char *)stackblock(); | 12354 | typeloc = out - (char *)stackblock(); |
12337 | STADJUST(1, out); | 12355 | STADJUST(1, out); |
@@ -12390,6 +12408,8 @@ parsesub: { | |||
12390 | static const char types[] ALIGN1 = "}-+?="; | 12408 | static const char types[] ALIGN1 = "}-+?="; |
12391 | /* ${VAR...} but not $VAR or ${#VAR} */ | 12409 | /* ${VAR...} but not $VAR or ${#VAR} */ |
12392 | /* c == first char after VAR */ | 12410 | /* c == first char after VAR */ |
12411 | int cc = c; | ||
12412 | |||
12393 | switch (c) { | 12413 | switch (c) { |
12394 | case ':': | 12414 | case ':': |
12395 | c = pgetc_eatbnl(); | 12415 | c = pgetc_eatbnl(); |
@@ -12414,21 +12434,24 @@ parsesub: { | |||
12414 | break; | 12434 | break; |
12415 | } | 12435 | } |
12416 | case '%': | 12436 | case '%': |
12417 | case '#': { | 12437 | case '#': |
12418 | int cc = c; | ||
12419 | subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); | 12438 | subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); |
12420 | c = pgetc_eatbnl(); | 12439 | c = pgetc_eatbnl(); |
12421 | if (c != cc) | 12440 | if (c == cc) |
12422 | goto badsub; | 12441 | subtype++; |
12423 | subtype++; | 12442 | else |
12443 | pungetc(); | ||
12444 | |||
12445 | newsyn = BASESYNTAX; | ||
12424 | break; | 12446 | break; |
12425 | } | ||
12426 | #if BASH_PATTERN_SUBST | 12447 | #if BASH_PATTERN_SUBST |
12427 | case '/': | 12448 | case '/': |
12428 | /* ${v/[/]pattern/repl} */ | 12449 | /* ${v/[/]pattern/repl} */ |
12429 | //TODO: encode pattern and repl separately. | 12450 | //TODO: encode pattern and repl separately. |
12430 | // Currently ${v/$var_with_slash/repl} is horribly broken | 12451 | // Currently cases like: v=1;echo ${v/$((1/1))/ONE} |
12452 | // are broken (should print "ONE") | ||
12431 | subtype = VSREPLACE; | 12453 | subtype = VSREPLACE; |
12454 | newsyn = BASESYNTAX; | ||
12432 | c = pgetc_eatbnl(); | 12455 | c = pgetc_eatbnl(); |
12433 | if (c != '/') | 12456 | if (c != '/') |
12434 | goto badsub; | 12457 | goto badsub; |
@@ -12440,11 +12463,24 @@ parsesub: { | |||
12440 | badsub: | 12463 | badsub: |
12441 | pungetc(); | 12464 | pungetc(); |
12442 | } | 12465 | } |
12466 | |||
12467 | if (newsyn == ARISYNTAX && subtype > VSNORMAL) | ||
12468 | newsyn = DQSYNTAX; | ||
12469 | |||
12470 | if (newsyn != synstack->syntax) { | ||
12471 | synstack_push(&synstack, | ||
12472 | synstack->prev ?: alloca(sizeof(*synstack)), | ||
12473 | newsyn); | ||
12474 | |||
12475 | synstack->varpushed = 1; | ||
12476 | synstack->dblquote = newsyn != BASESYNTAX; | ||
12477 | } | ||
12478 | |||
12443 | ((unsigned char *)stackblock())[typeloc] = subtype; | 12479 | ((unsigned char *)stackblock())[typeloc] = subtype; |
12444 | if (subtype != VSNORMAL) { | 12480 | if (subtype != VSNORMAL) { |
12445 | varnest++; | 12481 | synstack->varnest++; |
12446 | if (dblquote) | 12482 | if (synstack->dblquote) |
12447 | dqvarnest++; | 12483 | synstack->dqvarnest++; |
12448 | } | 12484 | } |
12449 | STPUTC('=', out); | 12485 | STPUTC('=', out); |
12450 | } | 12486 | } |
@@ -12501,7 +12537,7 @@ parsebackq: { | |||
12501 | case '\\': | 12537 | case '\\': |
12502 | pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */ | 12538 | pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */ |
12503 | if (pc != '\\' && pc != '`' && pc != '$' | 12539 | if (pc != '\\' && pc != '`' && pc != '$' |
12504 | && (!dblquote || pc != '"') | 12540 | && (!synstack->dblquote || pc != '"') |
12505 | ) { | 12541 | ) { |
12506 | STPUTC('\\', pout); | 12542 | STPUTC('\\', pout); |
12507 | } | 12543 | } |
@@ -12576,10 +12612,11 @@ parsebackq: { | |||
12576 | * Parse an arithmetic expansion (indicate start of one and set state) | 12612 | * Parse an arithmetic expansion (indicate start of one and set state) |
12577 | */ | 12613 | */ |
12578 | parsearith: { | 12614 | parsearith: { |
12579 | if (++arinest == 1) { | 12615 | |
12580 | prevsyntax = syntax; | 12616 | synstack_push(&synstack, |
12581 | syntax = ARISYNTAX; | 12617 | synstack->prev ?: alloca(sizeof(*synstack)), |
12582 | } | 12618 | ARISYNTAX); |
12619 | synstack->dblquote = 1; | ||
12583 | USTPUTC(CTLARI, out); | 12620 | USTPUTC(CTLARI, out); |
12584 | goto parsearith_return; | 12621 | goto parsearith_return; |
12585 | } | 12622 | } |
diff --git a/shell/ash_test/ash-arith/arith_nested1.right b/shell/ash_test/ash-arith/arith_nested1.right new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/shell/ash_test/ash-arith/arith_nested1.right | |||
@@ -0,0 +1 @@ | |||
1 | |||
diff --git a/shell/ash_test/ash-arith/arith_nested1.tests b/shell/ash_test/ash-arith/arith_nested1.tests new file mode 100755 index 000000000..28571b833 --- /dev/null +++ b/shell/ash_test/ash-arith/arith_nested1.tests | |||
@@ -0,0 +1 @@ | |||
echo $(( ( $((1)) ) )) | |||
diff --git a/shell/ash_test/ash-quoting/squote_in_varexp3.right b/shell/ash_test/ash-quoting/squote_in_varexp3.right new file mode 100644 index 000000000..223b7836f --- /dev/null +++ b/shell/ash_test/ash-quoting/squote_in_varexp3.right | |||
@@ -0,0 +1 @@ | |||
B | |||
diff --git a/shell/ash_test/ash-quoting/squote_in_varexp3.tests b/shell/ash_test/ash-quoting/squote_in_varexp3.tests new file mode 100755 index 000000000..028a88fd9 --- /dev/null +++ b/shell/ash_test/ash-quoting/squote_in_varexp3.tests | |||
@@ -0,0 +1 @@ | |||
x=\'B; echo "${x#\'}" | |||
diff --git a/shell/ash_test/ash-vars/var_bash3.right b/shell/ash_test/ash-vars/var_bash3.right index a97c850ea..8899d981c 100644 --- a/shell/ash_test/ash-vars/var_bash3.right +++ b/shell/ash_test/ash-vars/var_bash3.right | |||
@@ -1,6 +1,6 @@ | |||
1 | 1 a041#c | 1 | 1 a041#c |
2 | 2 a041#c | 2 | 2 a041#c |
3 | 3 a\041#c | 3 | 3 a041#c |
4 | 4 a\041#c | 4 | 4 a\041#c |
5 | 5 a\041#c | 5 | 5 a\041#c |
6 | 6 a\041#c | 6 | 6 a\041#c |
@@ -17,4 +17,4 @@ | |||
17 | 17 a\tc | 17 | 17 a\tc |
18 | 18 a\tc | 18 | 18 a\tc |
19 | 19 atc | 19 | 19 atc |
20 | 20 a\tc | 20 | 20 atc |
diff --git a/shell/ash_test/ash-vars/var_bash4.right b/shell/ash_test/ash-vars/var_bash4.right index 0ef1bf661..9067e58e6 100644 --- a/shell/ash_test/ash-vars/var_bash4.right +++ b/shell/ash_test/ash-vars/var_bash4.right | |||
@@ -3,26 +3,26 @@ Replace str: _\\_\z_ | |||
3 | Pattern: single backslash and star: "replace literal star" | 3 | Pattern: single backslash and star: "replace literal star" |
4 | Unquoted: a_\_z_b\*c | 4 | Unquoted: a_\_z_b\*c |
5 | Unquoted =: a_\_z_b\*c | 5 | Unquoted =: a_\_z_b\*c |
6 | Quoted: a_\_\z_b\*c | 6 | Quoted: a_\_z_b\*c |
7 | Quoted =: a_\_\z_b\*c | 7 | Quoted =: a_\_z_b\*c |
8 | Pattern: double backslash and star: "replace backslash and everything after it" | 8 | Pattern: double backslash and star: "replace backslash and everything after it" |
9 | Unquoted: a*b_\_z_ | 9 | Unquoted: a*b_\_z_ |
10 | Unquoted =: a*b_\_z_ | 10 | Unquoted =: a*b_\_z_ |
11 | Quoted: a*b_\_\z_ | 11 | Quoted: a*b_\_z_ |
12 | Quoted =: a*b_\_\z_ | 12 | Quoted =: a*b_\_z_ |
13 | 13 | ||
14 | Source: a\bc | 14 | Source: a\bc |
15 | Replace str: _\\_\z_ | 15 | Replace str: _\\_\z_ |
16 | Pattern: single backslash and b: "replace b" | 16 | Pattern: single backslash and b: "replace b" |
17 | Unquoted: a\_\_z_c | 17 | Unquoted: a\_\_z_c |
18 | Unquoted =: a\_\_z_c | 18 | Unquoted =: a\_\_z_c |
19 | Quoted: a\_\_\z_c | 19 | Quoted: a\_\_z_c |
20 | Quoted =: a\_\_\z_c | 20 | Quoted =: a\_\_z_c |
21 | Pattern: double backslash and b: "replace backslash and b" | 21 | Pattern: double backslash and b: "replace backslash and b" |
22 | Unquoted: a_\_z_c | 22 | Unquoted: a_\_z_c |
23 | Unquoted =: a_\_z_c | 23 | Unquoted =: a_\_z_c |
24 | Quoted: a_\_\z_c | 24 | Quoted: a_\_z_c |
25 | Quoted =: a_\_\z_c | 25 | Quoted =: a_\_z_c |
26 | 26 | ||
27 | Source: a\bc | 27 | Source: a\bc |
28 | Replace str: _\\_\z_ (as variable $s) | 28 | Replace str: _\\_\z_ (as variable $s) |
diff --git a/shell/ash_test/ash-vars/var_bash6.right b/shell/ash_test/ash-vars/var_bash6.right index 63fc23df8..115ff8b04 100644 --- a/shell/ash_test/ash-vars/var_bash6.right +++ b/shell/ash_test/ash-vars/var_bash6.right | |||
@@ -1,5 +1,5 @@ | |||
1 | Expected Actual | 1 | Expected Actual |
2 | a*z : a*z | 2 | a*z : a*z |
3 | \z : \z | 3 | z : z |
4 | a1z a2z: a1z a2z | 4 | a1z a2z: a1z a2z |
5 | z : z | 5 | z : z |
diff --git a/shell/ash_test/ash-vars/var_bash6.tests b/shell/ash_test/ash-vars/var_bash6.tests index cf2e4f020..686834177 100755 --- a/shell/ash_test/ash-vars/var_bash6.tests +++ b/shell/ash_test/ash-vars/var_bash6.tests | |||
@@ -3,7 +3,7 @@ | |||
3 | >a1z; >a2z; | 3 | >a1z; >a2z; |
4 | echo 'Expected' 'Actual' | 4 | echo 'Expected' 'Actual' |
5 | v='a bz'; echo 'a*z :' "${v/a*z/a*z}" | 5 | v='a bz'; echo 'a*z :' "${v/a*z/a*z}" |
6 | v='a bz'; echo '\z :' "${v/a*z/\z}" | 6 | v='a bz'; echo 'z :' "${v/a*z/\z}" |
7 | v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} | 7 | v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} |
8 | v='a bz'; echo 'z :' ${v/a*z/\z} | 8 | v='a bz'; echo 'z :' ${v/a*z/\z} |
9 | rm a1z a2z | 9 | rm a1z a2z |
diff --git a/shell/ash_test/ash-vars/var_bash7.right b/shell/ash_test/ash-vars/var_bash7.right new file mode 100644 index 000000000..223b7836f --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash7.right | |||
@@ -0,0 +1 @@ | |||
B | |||
diff --git a/shell/ash_test/ash-vars/var_bash7.tests b/shell/ash_test/ash-vars/var_bash7.tests new file mode 100755 index 000000000..c4ce03f7f --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash7.tests | |||
@@ -0,0 +1 @@ | |||
x=AB; echo "${x#$'\x41'}" | |||
diff --git a/shell/hush_test/hush-arith/arith_nested1.right b/shell/hush_test/hush-arith/arith_nested1.right new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/shell/hush_test/hush-arith/arith_nested1.right | |||
@@ -0,0 +1 @@ | |||
1 | |||
diff --git a/shell/hush_test/hush-arith/arith_nested1.tests b/shell/hush_test/hush-arith/arith_nested1.tests new file mode 100755 index 000000000..28571b833 --- /dev/null +++ b/shell/hush_test/hush-arith/arith_nested1.tests | |||
@@ -0,0 +1 @@ | |||
echo $(( ( $((1)) ) )) | |||
diff --git a/shell/hush_test/hush-quoting/squote_in_varexp3.right b/shell/hush_test/hush-quoting/squote_in_varexp3.right new file mode 100644 index 000000000..223b7836f --- /dev/null +++ b/shell/hush_test/hush-quoting/squote_in_varexp3.right | |||
@@ -0,0 +1 @@ | |||
B | |||
diff --git a/shell/hush_test/hush-quoting/squote_in_varexp3.tests b/shell/hush_test/hush-quoting/squote_in_varexp3.tests new file mode 100755 index 000000000..028a88fd9 --- /dev/null +++ b/shell/hush_test/hush-quoting/squote_in_varexp3.tests | |||
@@ -0,0 +1 @@ | |||
x=\'B; echo "${x#\'}" | |||
diff --git a/shell/hush_test/hush-vars/var_bash3.right b/shell/hush_test/hush-vars/var_bash3.right index a97c850ea..8899d981c 100644 --- a/shell/hush_test/hush-vars/var_bash3.right +++ b/shell/hush_test/hush-vars/var_bash3.right | |||
@@ -1,6 +1,6 @@ | |||
1 | 1 a041#c | 1 | 1 a041#c |
2 | 2 a041#c | 2 | 2 a041#c |
3 | 3 a\041#c | 3 | 3 a041#c |
4 | 4 a\041#c | 4 | 4 a\041#c |
5 | 5 a\041#c | 5 | 5 a\041#c |
6 | 6 a\041#c | 6 | 6 a\041#c |
@@ -17,4 +17,4 @@ | |||
17 | 17 a\tc | 17 | 17 a\tc |
18 | 18 a\tc | 18 | 18 a\tc |
19 | 19 atc | 19 | 19 atc |
20 | 20 a\tc | 20 | 20 atc |
diff --git a/shell/hush_test/hush-vars/var_bash4.right b/shell/hush_test/hush-vars/var_bash4.right index 0ef1bf661..9067e58e6 100644 --- a/shell/hush_test/hush-vars/var_bash4.right +++ b/shell/hush_test/hush-vars/var_bash4.right | |||
@@ -3,26 +3,26 @@ Replace str: _\\_\z_ | |||
3 | Pattern: single backslash and star: "replace literal star" | 3 | Pattern: single backslash and star: "replace literal star" |
4 | Unquoted: a_\_z_b\*c | 4 | Unquoted: a_\_z_b\*c |
5 | Unquoted =: a_\_z_b\*c | 5 | Unquoted =: a_\_z_b\*c |
6 | Quoted: a_\_\z_b\*c | 6 | Quoted: a_\_z_b\*c |
7 | Quoted =: a_\_\z_b\*c | 7 | Quoted =: a_\_z_b\*c |
8 | Pattern: double backslash and star: "replace backslash and everything after it" | 8 | Pattern: double backslash and star: "replace backslash and everything after it" |
9 | Unquoted: a*b_\_z_ | 9 | Unquoted: a*b_\_z_ |
10 | Unquoted =: a*b_\_z_ | 10 | Unquoted =: a*b_\_z_ |
11 | Quoted: a*b_\_\z_ | 11 | Quoted: a*b_\_z_ |
12 | Quoted =: a*b_\_\z_ | 12 | Quoted =: a*b_\_z_ |
13 | 13 | ||
14 | Source: a\bc | 14 | Source: a\bc |
15 | Replace str: _\\_\z_ | 15 | Replace str: _\\_\z_ |
16 | Pattern: single backslash and b: "replace b" | 16 | Pattern: single backslash and b: "replace b" |
17 | Unquoted: a\_\_z_c | 17 | Unquoted: a\_\_z_c |
18 | Unquoted =: a\_\_z_c | 18 | Unquoted =: a\_\_z_c |
19 | Quoted: a\_\_\z_c | 19 | Quoted: a\_\_z_c |
20 | Quoted =: a\_\_\z_c | 20 | Quoted =: a\_\_z_c |
21 | Pattern: double backslash and b: "replace backslash and b" | 21 | Pattern: double backslash and b: "replace backslash and b" |
22 | Unquoted: a_\_z_c | 22 | Unquoted: a_\_z_c |
23 | Unquoted =: a_\_z_c | 23 | Unquoted =: a_\_z_c |
24 | Quoted: a_\_\z_c | 24 | Quoted: a_\_z_c |
25 | Quoted =: a_\_\z_c | 25 | Quoted =: a_\_z_c |
26 | 26 | ||
27 | Source: a\bc | 27 | Source: a\bc |
28 | Replace str: _\\_\z_ (as variable $s) | 28 | Replace str: _\\_\z_ (as variable $s) |
diff --git a/shell/hush_test/hush-vars/var_bash6.right b/shell/hush_test/hush-vars/var_bash6.right index 63fc23df8..115ff8b04 100644 --- a/shell/hush_test/hush-vars/var_bash6.right +++ b/shell/hush_test/hush-vars/var_bash6.right | |||
@@ -1,5 +1,5 @@ | |||
1 | Expected Actual | 1 | Expected Actual |
2 | a*z : a*z | 2 | a*z : a*z |
3 | \z : \z | 3 | z : z |
4 | a1z a2z: a1z a2z | 4 | a1z a2z: a1z a2z |
5 | z : z | 5 | z : z |
diff --git a/shell/hush_test/hush-vars/var_bash6.tests b/shell/hush_test/hush-vars/var_bash6.tests index cf2e4f020..686834177 100755 --- a/shell/hush_test/hush-vars/var_bash6.tests +++ b/shell/hush_test/hush-vars/var_bash6.tests | |||
@@ -3,7 +3,7 @@ | |||
3 | >a1z; >a2z; | 3 | >a1z; >a2z; |
4 | echo 'Expected' 'Actual' | 4 | echo 'Expected' 'Actual' |
5 | v='a bz'; echo 'a*z :' "${v/a*z/a*z}" | 5 | v='a bz'; echo 'a*z :' "${v/a*z/a*z}" |
6 | v='a bz'; echo '\z :' "${v/a*z/\z}" | 6 | v='a bz'; echo 'z :' "${v/a*z/\z}" |
7 | v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} | 7 | v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} |
8 | v='a bz'; echo 'z :' ${v/a*z/\z} | 8 | v='a bz'; echo 'z :' ${v/a*z/\z} |
9 | rm a1z a2z | 9 | rm a1z a2z |