ash: expand: Do not reprocess data when expanding words

Upstream patch:

    Date: Wed, 30 May 2018 02:06:03 +0800
    expand: Do not reprocess data when expanding words

    Currently various paths will reprocess data when performing word
    expansion.  For example, expari will skip backwards looking for
    the start of the arithmetic expansion, while evalvar will skip
    unexpanded words manually.

    This is cumbersome and error-prone.  This patch fixes this by
    making word expansions proceed in a linear fashion.  This means
    changing argstr and the various expansion functions such as expari
    and subevalvar to return the next character to be expanded.

    This is inspired by similar code from FreeBSD.  However, we take
    things one step further and completely remove the manual word
    skipping in evalvar.  This is accomplished by introducing a new
    EXP_DISCARD flag that tells argstr to only parse and not produce
    any actual expansions.

    Incidentally, argstr will now always NUL-terminate the expansion
    unless the EXP_WORD flag is set.  This is because all but one
    caller of argstr wants the result to be NUL-termianted.

    Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Also includes two one-line follow-up fixes:

    expand: Eat closing brace for length parameter
            if (subtype == VSLENGTH) {
    +               p++;
                    if (flag & EXP_DISCARD)
    expand: Fix double-decrement in argstr
    -               newloc = expdest - (char *)stackblock() - end;
    +               newloc = q - (char *)stackblock() - end;

and changes in code for bash substring extensions.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2020-02-24 10:02:50 +01:00
parent 6cda0b04a3
commit 823318822c

View File

@ -6035,6 +6035,7 @@ static int substr_atoi(const char *s)
#define EXP_WORD 0x40 /* expand word in parameter expansion */ #define EXP_WORD 0x40 /* expand word in parameter expansion */
#define EXP_QUOTED 0x100 /* expand word in double quotes */ #define EXP_QUOTED 0x100 /* expand word in double quotes */
#define EXP_KEEPNUL 0x200 /* do not skip NUL characters */ #define EXP_KEEPNUL 0x200 /* do not skip NUL characters */
#define EXP_DISCARD 0x400 /* discard result of expansion */
/* /*
* rmescape() flags * rmescape() flags
@ -6452,13 +6453,15 @@ removerecordregions(int endoff)
} }
static char * static char *
exptilde(char *startp, char *p, int flag) exptilde(char *startp, int flag)
{ {
unsigned char c; unsigned char c;
char *name; char *name;
struct passwd *pw; struct passwd *pw;
const char *home; const char *home;
char *p;
p = startp;
name = p + 1; name = p + 1;
while ((c = *++p) != '\0') { while ((c = *++p) != '\0') {
@ -6477,6 +6480,8 @@ exptilde(char *startp, char *p, int flag)
} }
} }
done: done:
if (flag & EXP_DISCARD)
goto out;
*p = '\0'; *p = '\0';
if (*name == '\0') { if (*name == '\0') {
home = lookupvar("HOME"); home = lookupvar("HOME");
@ -6486,13 +6491,13 @@ exptilde(char *startp, char *p, int flag)
goto lose; goto lose;
home = pw->pw_dir; home = pw->pw_dir;
} }
*p = c;
if (!home) if (!home)
goto lose; goto lose;
*p = c;
strtodest(home, flag | EXP_QUOTED); strtodest(home, flag | EXP_QUOTED);
out:
return p; return p;
lose: lose:
*p = c;
return startp; return startp;
} }
@ -6591,6 +6596,9 @@ expbackq(union node *cmd, int flag)
int startloc; int startloc;
struct stackmark smark; struct stackmark smark;
if (flag & EXP_DISCARD)
goto out;
INT_OFF; INT_OFF;
startloc = expdest - (char *)stackblock(); startloc = expdest - (char *)stackblock();
pushstackmark(&smark, startloc); pushstackmark(&smark, startloc);
@ -6632,64 +6640,57 @@ expbackq(union node *cmd, int flag)
(int)((dest - (char *)stackblock()) - startloc), (int)((dest - (char *)stackblock()) - startloc),
(int)((dest - (char *)stackblock()) - startloc), (int)((dest - (char *)stackblock()) - startloc),
stackblock() + startloc)); stackblock() + startloc));
out:
argbackq = argbackq->next;
} }
/* expari needs it */
static char *argstr(char *p, int flag);
#if ENABLE_FEATURE_SH_MATH #if ENABLE_FEATURE_SH_MATH
/* /*
* Expand arithmetic expression. Backup to start of expression, * Expand arithmetic expression. Backup to start of expression,
* evaluate, place result in (backed up) result, adjust string position. * evaluate, place result in (backed up) result, adjust string position.
*/ */
static void static char *
expari(int flag) expari(char *start, int flag)
{ {
char *p, *start; struct stackmark sm;
int begoff; int begoff;
int endoff;
int len; int len;
arith_t result;
char *p;
/* ifsfree(); */ p = stackblock();
begoff = expdest - p;
p = argstr(start, flag & EXP_DISCARD);
if (flag & EXP_DISCARD)
goto out;
/*
* This routine is slightly over-complicated for
* efficiency. Next we scan backwards looking for the
* start of arithmetic.
*/
start = stackblock(); start = stackblock();
p = expdest - 1; endoff = expdest - start;
*p = '\0'; start += begoff;
p--; STADJUST(start - expdest, expdest);
while (1) {
int esc;
while ((unsigned char)*p != CTLARI) {
p--;
#if DEBUG
if (p < start) {
ash_msg_and_raise_error("missing CTLARI (shouldn't happen)");
}
#endif
}
esc = esclen(start, p);
if (!(esc % 2)) {
break;
}
p -= esc + 1;
}
begoff = p - start;
removerecordregions(begoff); removerecordregions(begoff);
expdest = p;
if (flag & QUOTES_ESC) if (flag & QUOTES_ESC)
rmescapes(p + 1, 0, NULL); rmescapes(start, 0, NULL);
len = cvtnum(ash_arith(p + 1), flag); pushstackmark(&sm, endoff);
result = ash_arith(start);
popstackmark(&sm);
len = cvtnum(result, flag);
if (!(flag & EXP_QUOTED)) if (!(flag & EXP_QUOTED))
recordregion(begoff, begoff + len, 0); recordregion(begoff, begoff + len, 0);
out:
return p;
} }
#endif #endif
@ -6701,7 +6702,7 @@ static char *evalvar(char *p, int flags);
* characters to allow for further processing. Otherwise treat * characters to allow for further processing. Otherwise treat
* $@ like $* since no splitting will be performed. * $@ like $* since no splitting will be performed.
*/ */
static void static char *
argstr(char *p, int flag) argstr(char *p, int flag)
{ {
static const char spclchars[] ALIGN1 = { static const char spclchars[] ALIGN1 = {
@ -6713,6 +6714,7 @@ argstr(char *p, int flag)
CTLVAR, CTLVAR,
CTLBACKQ, CTLBACKQ,
#if ENABLE_FEATURE_SH_MATH #if ENABLE_FEATURE_SH_MATH
CTLARI,
CTLENDARI, CTLENDARI,
#endif #endif
'\0' '\0'
@ -6723,41 +6725,45 @@ argstr(char *p, int flag)
size_t length; size_t length;
int startloc; int startloc;
if (!(flag & EXP_VARTILDE)) { reject += !!(flag & EXP_VARTILDE2);
reject += 2; reject += flag & EXP_VARTILDE ? 0 : 2;
} else if (flag & EXP_VARTILDE2) {
reject++;
}
inquotes = 0; inquotes = 0;
length = 0; length = 0;
if (flag & EXP_TILDE) { if (flag & EXP_TILDE) {
char *q;
flag &= ~EXP_TILDE; flag &= ~EXP_TILDE;
tilde: tilde:
q = p; if (*p == '~')
if (*q == '~') p = exptilde(p, flag);
p = exptilde(p, q, flag);
} }
start: start:
startloc = expdest - (char *)stackblock(); startloc = expdest - (char *)stackblock();
for (;;) { for (;;) {
int end;
unsigned char c; unsigned char c;
length += strcspn(p + length, reject); length += strcspn(p + length, reject);
end = 0;
c = p[length]; c = p[length];
if (c) { if (!(c & 0x80)
if (!(c & 0x80) IF_FEATURE_SH_MATH(|| c == CTLENDARI)
IF_FEATURE_SH_MATH(|| c == CTLENDARI) || c == CTLENDVAR
) { ) {
/* c == '=' || c == ':' || c == CTLENDARI */ /*
length++; * c == '=' || c == ':' || c == '\0' ||
} * c == CTLENDARI || c == CTLENDVAR
*/
length++;
/* c == '\0' || c == CTLENDARI || c == CTLENDVAR */
end = !!((c - 1) & 0x80);
} }
if (length > 0) { if (length > 0 && !(flag & EXP_DISCARD)) {
int newloc; int newloc;
expdest = stnputs(p, length, expdest); char *q;
newloc = expdest - (char *)stackblock();
q = stnputs(p, length, expdest);
q[-1] &= end - 1;
expdest = q - (flag & EXP_WORD ? end : 0);
newloc = q - (char *)stackblock() - end;
if (breakall && !inquotes && newloc > startloc) { if (breakall && !inquotes && newloc > startloc) {
recordregion(startloc, newloc, 0); recordregion(startloc, newloc, 0);
} }
@ -6766,14 +6772,11 @@ argstr(char *p, int flag)
p += length + 1; p += length + 1;
length = 0; length = 0;
if (end)
break;
switch (c) { switch (c) {
case '\0':
goto breakloop;
case '=': case '=':
if (flag & EXP_VARTILDE2) {
p--;
continue;
}
flag |= EXP_VARTILDE2; flag |= EXP_VARTILDE2;
reject++; reject++;
/* fall through */ /* fall through */
@ -6786,11 +6789,6 @@ argstr(char *p, int flag)
goto tilde; goto tilde;
} }
continue; continue;
}
switch (c) {
case CTLENDVAR: /* ??? */
goto breakloop;
case CTLQUOTEMARK: case CTLQUOTEMARK:
/* "$@" syntax adherence hack */ /* "$@" syntax adherence hack */
if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
@ -6816,17 +6814,15 @@ argstr(char *p, int flag)
goto start; goto start;
case CTLBACKQ: case CTLBACKQ:
expbackq(argbackq->n, flag | inquotes); expbackq(argbackq->n, flag | inquotes);
argbackq = argbackq->next;
goto start; goto start;
#if ENABLE_FEATURE_SH_MATH #if ENABLE_FEATURE_SH_MATH
case CTLENDARI: case CTLARI:
p--; p = expari(p, flag | inquotes);
expari(flag | inquotes);
goto start; goto start;
#endif #endif
} }
} }
breakloop: ; return p - 1;
} }
static char * static char *
@ -6951,25 +6947,27 @@ varunset(const char *end, const char *var, const char *umsg, int varflags)
ash_msg_and_raise_error("%.*s: %s%s", (int)(end - var - 1), var, msg, tail); ash_msg_and_raise_error("%.*s: %s%s", (int)(end - var - 1), var, msg, tail);
} }
static const char * static char *
subevalvar(char *p, char *str, int strloc, int subtype, subevalvar(char *start, char *str, int strloc,
int startloc, int varflags, int flag) int startloc, int varflags, int flag)
{ {
struct nodelist *saveargbackq = argbackq; int subtype = varflags & VSTYPE;
int quotes = flag & QUOTES_ESC; int quotes = flag & QUOTES_ESC;
char *startp; char *startp;
char *loc; char *loc;
char *rmesc, *rmescend; char *rmesc, *rmescend;
int amount, resetloc; long amount;
int resetloc;
int argstr_flags; int argstr_flags;
IF_BASH_PATTERN_SUBST(int workloc;) IF_BASH_PATTERN_SUBST(int workloc;)
IF_BASH_PATTERN_SUBST(int slash_pos;) IF_BASH_PATTERN_SUBST(int slash_pos;)
IF_BASH_PATTERN_SUBST(char *repl;) IF_BASH_PATTERN_SUBST(char *repl;)
int zero; int zero;
char *(*scan)(char*, char*, char*, char*, int, int); char *(*scan)(char*, char*, char*, char*, int, int);
char *p;
//bb_error_msg("subevalvar(p:'%s',str:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)", //bb_error_msg("subevalvar(start:'%s',str:'%s',strloc:%d,startloc:%d,varflags:%x,quotes:%d)",
// p, str, strloc, subtype, startloc, varflags, quotes); // start, str, strloc, startloc, varflags, quotes);
#if BASH_PATTERN_SUBST #if BASH_PATTERN_SUBST
/* For "${v/pattern/repl}", we must find the delimiter _before_ /* For "${v/pattern/repl}", we must find the delimiter _before_
@ -6979,7 +6977,7 @@ subevalvar(char *p, char *str, int strloc, int subtype,
repl = NULL; repl = NULL;
if (subtype == VSREPLACE || subtype == VSREPLACEALL) { if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
/* Find '/' and replace with NUL */ /* Find '/' and replace with NUL */
repl = p; repl = start;
/* The pattern can't be empty. /* The pattern can't be empty.
* IOW: if the first char after "${v//" is a slash, * IOW: if the first char after "${v//" is a slash,
* it does not terminate the pattern - it's the first char of the pattern: * it does not terminate the pattern - it's the first char of the pattern:
@ -7004,17 +7002,17 @@ subevalvar(char *p, char *str, int strloc, int subtype,
} }
} }
#endif #endif
argstr_flags = EXP_TILDE; argstr_flags = (flag & EXP_DISCARD) | EXP_TILDE;
if (subtype != VSASSIGN if (!str
&& subtype != VSQUESTION
#if BASH_SUBSTR #if BASH_SUBSTR
&& subtype != VSSUBSTR && subtype != VSSUBSTR
#endif #endif
) { ) {
/* EXP_CASE keeps CTLESC's */ /* EXP_CASE keeps CTLESC's */
argstr_flags = EXP_TILDE | EXP_CASE; argstr_flags |= EXP_CASE;
} }
argstr(p, argstr_flags); p = argstr(start, argstr_flags);
//bb_error_msg("str0:'%s'", (char *)stackblock() + strloc); //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc);
#if BASH_PATTERN_SUBST #if BASH_PATTERN_SUBST
slash_pos = -1; slash_pos = -1;
@ -7022,24 +7020,25 @@ subevalvar(char *p, char *str, int strloc, int subtype,
slash_pos = expdest - ((char *)stackblock() + strloc); slash_pos = expdest - ((char *)stackblock() + strloc);
STPUTC('/', expdest); STPUTC('/', expdest);
//bb_error_msg("repl+1:'%s'", repl + 1); //bb_error_msg("repl+1:'%s'", repl + 1);
argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */ p = argstr(repl + 1, (flag & EXP_DISCARD) | EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */
*repl = '/'; *repl = '/';
} }
#endif #endif
STPUTC('\0', expdest); if (flag & EXP_DISCARD)
argbackq = saveargbackq; return p;
startp = (char *)stackblock() + startloc; startp = (char *)stackblock() + startloc;
//bb_error_msg("str1:'%s'", (char *)stackblock() + strloc); //bb_error_msg("str1:'%s'", (char *)stackblock() + strloc);
switch (subtype) { switch (subtype) {
case VSASSIGN: case VSASSIGN:
setvar0(str, startp); setvar0(str, startp);
amount = startp - expdest;
STADJUST(amount, expdest); loc = startp;
return startp; goto out;
case VSQUESTION: case VSQUESTION:
varunset(p, str, startp, varflags); varunset(start, str, startp, varflags);
/* NOTREACHED */ /* NOTREACHED */
#if BASH_SUBSTR #if BASH_SUBSTR
@ -7110,9 +7109,7 @@ subevalvar(char *p, char *str, int strloc, int subtype,
*loc++ = *vstr++; *loc++ = *vstr++;
} }
*loc = '\0'; *loc = '\0';
amount = loc - expdest; goto out;
STADJUST(amount, expdest);
return loc;
} }
#endif /* BASH_SUBSTR */ #endif /* BASH_SUBSTR */
} }
@ -7178,7 +7175,7 @@ subevalvar(char *p, char *str, int strloc, int subtype,
/* If there's no pattern to match, return the expansion unmolested */ /* If there's no pattern to match, return the expansion unmolested */
if (str[0] == '\0') if (str[0] == '\0')
return NULL; goto out1;
len = 0; len = 0;
idx = startp; idx = startp;
@ -7259,9 +7256,8 @@ subevalvar(char *p, char *str, int strloc, int subtype,
startp = (char *)stackblock() + startloc; startp = (char *)stackblock() + startloc;
memmove(startp, (char *)stackblock() + workloc, len + 1); memmove(startp, (char *)stackblock() + workloc, len + 1);
//bb_error_msg("startp:'%s'", startp); //bb_error_msg("startp:'%s'", startp);
amount = expdest - (startp + len); loc = startp + len;
STADJUST(-amount, expdest); goto out;
return startp;
} }
#endif /* BASH_PATTERN_SUBST */ #endif /* BASH_PATTERN_SUBST */
@ -7282,10 +7278,17 @@ subevalvar(char *p, char *str, int strloc, int subtype,
loc = startp + (str - loc) - 1; loc = startp + (str - loc) - 1;
} }
*loc = '\0'; *loc = '\0';
amount = loc - expdest; } else
STADJUST(amount, expdest); loc = str - 1;
}
return loc; out:
amount = loc - expdest;
STADJUST(amount, expdest);
out1:
/* Remove any recorded regions beyond start of variable */
removerecordregions(startloc);
return p;
} }
/* /*
@ -7310,7 +7313,14 @@ varvalue(char *name, int varflags, int flags, int quoted)
ssize_t len = 0; ssize_t len = 0;
int sep; int sep;
int subtype = varflags & VSTYPE; int subtype = varflags & VSTYPE;
int discard = subtype == VSPLUS || subtype == VSLENGTH; int discard = (subtype == VSPLUS || subtype == VSLENGTH) | (flags & EXP_DISCARD);
if (!subtype) {
if (discard)
return -1;
raise_error_syntax("bad substitution");
}
flags |= EXP_KEEPNUL; flags |= EXP_KEEPNUL;
flags &= discard ? ~QUOTES_ESC : ~0; flags &= discard ? ~QUOTES_ESC : ~0;
@ -7427,6 +7437,7 @@ varvalue(char *name, int varflags, int flags, int quoted)
if (discard) if (discard)
STADJUST(-len, expdest); STADJUST(-len, expdest);
return len; return len;
} }
@ -7439,18 +7450,15 @@ evalvar(char *p, int flag)
{ {
char varflags; char varflags;
char subtype; char subtype;
int quoted;
char *var; char *var;
int patloc; int patloc;
int startloc; int startloc;
ssize_t varlen; ssize_t varlen;
int quoted;
varflags = (unsigned char) *p++; varflags = (unsigned char) *p++;
subtype = varflags & VSTYPE; subtype = varflags & VSTYPE;
if (!subtype)
raise_error_syntax("bad substitution");
quoted = flag & EXP_QUOTED; quoted = flag & EXP_QUOTED;
var = p; var = p;
startloc = expdest - (char *)stackblock(); startloc = expdest - (char *)stackblock();
@ -7461,35 +7469,29 @@ evalvar(char *p, int flag)
if (varflags & VSNUL) if (varflags & VSNUL)
varlen--; varlen--;
if (subtype == VSPLUS) { switch (subtype) {
case VSPLUS:
varlen = -1 - varlen; varlen = -1 - varlen;
goto vsplus; /* fall through */
} case 0:
case VSMINUS:
if (subtype == VSMINUS) { p = argstr(p, flag | EXP_TILDE | EXP_WORD);
vsplus: if (varlen < 0)
if (varlen < 0) { return p;
argstr(
p,
flag | EXP_TILDE | EXP_WORD
);
goto end;
}
goto record; goto record;
}
if (subtype == VSASSIGN || subtype == VSQUESTION) { case VSASSIGN:
case VSQUESTION:
if (varlen >= 0) if (varlen >= 0)
goto record; goto record;
subevalvar(p, var, 0, subtype, startloc, varflags, p = subevalvar(p, var, 0, startloc, varflags,
flag & ~QUOTES_ESC); flag & ~QUOTES_ESC);
if (flag & EXP_DISCARD)
return p;
varflags &= ~VSNUL; varflags &= ~VSNUL;
/*
* Remove any recorded regions beyond
* start of variable
*/
removerecordregions(startloc);
goto again; goto again;
} }
@ -7497,20 +7499,15 @@ evalvar(char *p, int flag)
varunset(p, var, 0, 0); varunset(p, var, 0, 0);
if (subtype == VSLENGTH) { if (subtype == VSLENGTH) {
p++;
if (flag & EXP_DISCARD)
return p;
cvtnum(varlen > 0 ? varlen : 0, flag); cvtnum(varlen > 0 ? varlen : 0, flag);
goto record; goto record;
} }
if (subtype == VSNORMAL) { if (subtype == VSNORMAL)
record: goto record;
if (quoted) {
quoted = *var == '@' && shellparam.nparam;
if (!quoted)
goto end;
}
recordregion(startloc, expdest - (char *)stackblock(), quoted);
goto end;
}
#if DEBUG #if DEBUG
switch (subtype) { switch (subtype) {
@ -7531,46 +7528,28 @@ evalvar(char *p, int flag)
} }
#endif #endif
if (varlen >= 0) { flag |= varlen < 0 ? EXP_DISCARD : 0;
if (!(flag & EXP_DISCARD)) {
/* /*
* Terminate the string and start recording the pattern * Terminate the string and start recording the pattern
* right after it * right after it
*/ */
STPUTC('\0', expdest); STPUTC('\0', expdest);
patloc = expdest - (char *)stackblock();
if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
startloc, varflags, flag)) {
int amount = expdest - (
(char *)stackblock() + patloc - 1
);
STADJUST(-amount, expdest);
}
/* Remove any recorded regions beyond start of variable */
removerecordregions(startloc);
goto record;
} }
varlen = 0; patloc = expdest - (char *)stackblock();
p = subevalvar(p, NULL, patloc, startloc, varflags, flag);
end: record:
if (subtype != VSNORMAL) { /* skip to end of alternative */ if (flag & EXP_DISCARD)
int nesting = 1; return p;
for (;;) {
unsigned char c = *p++; if (quoted) {
if (c == CTLESC) quoted = *var == '@' && shellparam.nparam;
p++; if (!quoted)
else if (c == CTLBACKQ) { return p;
if (varlen >= 0)
argbackq = argbackq->next;
} else if (c == CTLVAR) {
if ((*p++ & VSTYPE) != VSNORMAL)
nesting++;
} else if (c == CTLENDVAR) {
if (--nesting == 0)
break;
}
}
} }
recordregion(startloc, expdest - (char *)stackblock(), quoted);
return p; return p;
} }
@ -7983,13 +7962,11 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
STARTSTACKSTR(expdest); STARTSTACKSTR(expdest);
TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag)); TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
argstr(arg->narg.text, flag); argstr(arg->narg.text, flag);
p = _STPUTC('\0', expdest);
expdest = p - 1;
if (arglist == NULL) { if (arglist == NULL) {
/* here document expanded */ /* here document expanded */
goto out; goto out;
} }
p = grabstackstr(p); p = grabstackstr(expdest);
TRACE(("expandarg: p:'%s'\n", p)); TRACE(("expandarg: p:'%s'\n", p));
exparg.lastp = &exparg.list; exparg.lastp = &exparg.list;
/* /*
@ -8050,7 +8027,6 @@ casematch(union node *pattern, char *val)
argbackq = pattern->narg.backquote; argbackq = pattern->narg.backquote;
STARTSTACKSTR(expdest); STARTSTACKSTR(expdest);
argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
STACKSTRNUL(expdest);
ifsfree(); ifsfree();
result = patmatch(stackblock(), val); result = patmatch(stackblock(), val);
popstackmark(&smark); popstackmark(&smark);