From 03cbcf6532e8656e0bc4bc53feaa8e593e42e14d Mon Sep 17 00:00:00 2001 From: sjg Date: Thu, 11 May 2006 15:37:07 +0000 Subject: Extract the variable modifier logic to a separate function. This cuts Var_Parse in half! and allows the modifier logic to be used recursively - when getting modifiers via variables. Add new unit-test, to check that certain error cases are handled correctly. --- usr.bin/make/unit-tests/Makefile | 3 +- usr.bin/make/unit-tests/moderrs | 36 + usr.bin/make/unit-tests/modmisc | 19 +- usr.bin/make/unit-tests/test.exp | 23 + usr.bin/make/var.c | 2306 +++++++++++++++++++------------------- 5 files changed, 1252 insertions(+), 1135 deletions(-) create mode 100644 usr.bin/make/unit-tests/moderrs (limited to 'usr.bin/make') diff --git a/usr.bin/make/unit-tests/Makefile b/usr.bin/make/unit-tests/Makefile index ef05c8a7027..928892022c9 100644 --- a/usr.bin/make/unit-tests/Makefile +++ b/usr.bin/make/unit-tests/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.19 2006/02/26 22:45:46 apb Exp $ +# $NetBSD: Makefile,v 1.20 2006/05/11 15:37:07 sjg Exp $ # # Unit tests for make(1) # The main targets are: @@ -22,6 +22,7 @@ SUBFILES= \ comment \ cond1 \ dotwait \ + moderrs \ modmatch \ modmisc \ modorder \ diff --git a/usr.bin/make/unit-tests/moderrs b/usr.bin/make/unit-tests/moderrs new file mode 100644 index 00000000000..e8292e70c23 --- /dev/null +++ b/usr.bin/make/unit-tests/moderrs @@ -0,0 +1,36 @@ +# $Id: moderrs,v 1.1 2006/05/11 15:37:07 sjg Exp $ +# +# various modifier error tests + +VAR=TheVariable +# incase we have to change it ;-) +MOD_UNKN=Z +MOD_TERM=S,V,v +MOD_S:= ${MOD_TERM}, + +all: modunkn modunknV varterm vartermV modterm modtermV + +modunkn: + @echo "Expect: Unknown modifier 'Z'" + @echo "VAR:Z=${VAR:Z}" + +modunknV: + @echo "Expect: Unknown modifier 'Z'" + @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}" + +varterm: + @echo "Expect: Unclosed variable specification for VAR" + @echo VAR:S,V,v,=${VAR:S,V,v, + +vartermV: + @echo "Expect: Unclosed variable specification for VAR" + @echo VAR:${MOD_TERM},=${VAR:${MOD_S} + +modterm: + @echo "Expect: Unclosed substitution for VAR (, missing)" + @echo "and: Syntax error: Unterminated quoted string" + -@echo "VAR:S,V,v=${VAR:S,V,v}" + +modtermV: + @echo "Expect: Unclosed substitution for VAR (, missing)" + -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" diff --git a/usr.bin/make/unit-tests/modmisc b/usr.bin/make/unit-tests/modmisc index bc66711bb73..11f22eaed5a 100644 --- a/usr.bin/make/unit-tests/modmisc +++ b/usr.bin/make/unit-tests/modmisc @@ -1,4 +1,4 @@ -# $Id: modmisc,v 1.3 2006/02/26 22:40:50 wiz Exp $ +# $Id: modmisc,v 1.4 2006/05/11 15:37:07 sjg Exp $ # # miscellaneous modifier tests @@ -8,11 +8,26 @@ MOD_NODOT=S/:/ /g:N.:ts: # and decorate, note that $'s need to be doubled. Also note that # the modifier_variable can be used with other modifiers. MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@ +# another mod - pretend it is more interesting +MOD_HOMES=S,/home/,/homes/, +MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ +MOD_SEP=S,:, ,g -all: modvar +all: modvar modvarloop modvar: @echo "path='${path}'" @echo "path='${path:${MOD_NODOT}}'" @echo "path='${path:S,home,homes,:${MOD_NODOT}}'" @echo "path=${path:${MOD_NODOTX}:ts:}" + @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}" + +.for d in ${path:${MOD_SEP}:N.} /usr/xbin +path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ +paths+= ${d:${MOD_OPT}:${MOD_HOMES}} +.endfor + +modvarloop: + @echo "path_/usr/xbin=${path_/usr/xbin}" + @echo "paths=${paths}" + @echo "PATHS=${paths:tu}" diff --git a/usr.bin/make/unit-tests/test.exp b/usr.bin/make/unit-tests/test.exp index be5e203c326..84223d850fe 100644 --- a/usr.bin/make/unit-tests/test.exp +++ b/usr.bin/make/unit-tests/test.exp @@ -49,6 +49,25 @@ make: Graph cycles through `cycle.2.98' make: Graph cycles through `cycle.2.97' cycle.1.99 cycle.1.99 +Expect: Unknown modifier 'Z' +make: Unknown modifier 'Z' +VAR:Z= +Expect: Unknown modifier 'Z' +make: Unknown modifier 'Z' +VAR:Z= +Expect: Unclosed variable specification for VAR +make: Unclosed variable specification for VAR +VAR:S,V,v,=Thevariable +Expect: Unclosed variable specification for VAR +VAR:S,V,v,=Thevariable +Expect: Unclosed substitution for VAR (, missing) +and: Syntax error: Unterminated quoted string +make: Unclosed substitution for VAR (, missing) +Syntax error: Unterminated quoted string +Expect: Unclosed substitution for VAR (, missing) +make: Unclosed substitution for VAR (, missing) +VAR:S,V,v= +*** Error code 2 (ignored) LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a" LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a" LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A" @@ -68,6 +87,10 @@ path=':/bin:/usr/bin::/sbin:/usr/sbin:.:/home/user/bin:.' path='/bin:/usr/bin:/sbin:/usr/sbin:/home/user/bin' path='/bin:/usr/bin:/sbin:/usr/sbin:/homes/user/bin' path='/bin':'/usr/bin':'/sbin':'/usr/sbin':'/home/user/bin' +path='/bin':'/usr/bin':'/sbin':'/usr/sbin':'/homes/user/bin' +path_/usr/xbin=/opt/xbin/ +paths=/bin /usr/bin /sbin /usr/sbin /homes/user/bin /opt/xbin +PATHS=/BIN /USR/BIN /SBIN /USR/SBIN /HOMES/USER/BIN /OPT/XBIN LIST = one two three four five six seven eigth nine ten LIST:O = eigth five four nine one seven six ten three two # Note that 1 in every 10! trials two independently generated diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c index a670467b423..11f46ea47e1 100644 --- a/usr.bin/make/var.c +++ b/usr.bin/make/var.c @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.107 2006/04/22 19:40:40 christos Exp $ */ +/* $NetBSD: var.c,v 1.108 2006/05/11 15:37:07 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -69,14 +69,14 @@ */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: var.c,v 1.107 2006/04/22 19:40:40 christos Exp $"; +static char rcsid[] = "$NetBSD: var.c,v 1.108 2006/05/11 15:37:07 sjg Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: var.c,v 1.107 2006/04/22 19:40:40 christos Exp $"); +__RCSID("$NetBSD: var.c,v 1.108 2006/05/11 15:37:07 sjg Exp $"); #endif #endif /* not lint */ #endif @@ -1882,427 +1882,159 @@ VarChangeCase(char *str, int upper) return str; } -/*- - *----------------------------------------------------------------------- - * Var_Parse -- - * Given the start of a variable invocation, extract the variable - * name and find its value, then modify it according to the - * specification. - * - * Input: - * str The string to parse - * ctxt The context for the variable - * err TRUE if undefined variables are an error - * lengthPtr OUT: The length of the specification - * freePtr OUT: TRUE if caller should free result - * - * Results: - * The (possibly-modified) value of the variable or var_Error if the - * specification is invalid. The length of the specification is - * placed in *lengthPtr (for invalid specifications, this is just - * 2...?). - * A Boolean in *freePtr telling whether the returned string should - * be freed by the caller. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- +/* + * Now we need to apply any modifiers the user wants applied. + * These are: + * :M words which match the given . + * is of the standard file + * wildcarding form. + * :N words which do not match the given . + * :S[1gW] + * Substitute for in the value + * :C[1gW] + * Substitute for regex in the value + * :H Substitute the head of each word + * :T Substitute the tail of each word + * :E Substitute the extension (minus '.') of + * each word + * :R Substitute the root of each word + * (pathname minus the suffix). + * :O ("Order") Alphabeticaly sort words in variable. + * :Ox ("intermiX") Randomize words in variable. + * :u ("uniq") Remove adjacent duplicate words. + * :tu Converts the variable contents to uppercase. + * :tl Converts the variable contents to lowercase. + * :ts[c] Sets varSpace - the char used to + * separate words to 'c'. If 'c' is + * omitted then no separation is used. + * :tW Treat the variable contents as a single + * word, even if it contains spaces. + * (Mnemonic: one big 'W'ord.) + * :tw Treat the variable contents as multiple + * space-separated words. + * (Mnemonic: many small 'w'ords.) + * :[index] Select a single word from the value. + * :[start..end] Select multiple words from the value. + * :[*] or :[0] Select the entire value, as a single + * word. Equivalent to :tW. + * :[@] Select the entire value, as multiple + * words. Undoes the effect of :[*]. + * Equivalent to :tw. + * :[#] Returns the number of words in the value. + * + * :?: + * If the variable evaluates to true, return + * true value, else return the second value. + * :lhs=rhs Like :S, but the rhs goes to the end of + * the invocation. + * :sh Treat the current value as a command + * to be run, new value is its output. + * The following added so we can handle ODE makefiles. + * :@@@ + * Assign a temporary local variable + * to the current value of each word in turn + * and replace each word with the result of + * evaluating + * :D Use as value if variable defined + * :U Use as value if variable undefined + * :L Use the name of the variable as the value. + * :P Use the path of the node that has the same + * name as the variable as the value. This + * basically includes an implied :L so that + * the common method of refering to the path + * of your dependent 'x' in a rule is to use + * the form '${x:P}'. + * :!! Run cmd much the same as :sh run's the + * current value of the variable. + * The ::= modifiers, actually assign a value to the variable. + * Their main purpose is in supporting modifiers of .for loop + * iterators and other obscure uses. They always expand to + * nothing. In a target rule that would otherwise expand to an + * empty line they can be preceded with @: to keep make happy. + * Eg. + * + * foo: .USE + * .for i in ${.TARGET} ${.TARGET:R}.gz + * @: ${t::=$i} + * @echo blah ${t:T} + * .endfor + * + * ::= Assigns as the new value of variable. + * ::?= Assigns as value of variable if + * it was not already set. + * ::+= Appends to variable. + * ::!= Assigns output of as the new value of + * variable. */ -/* coverity[+alloc : arg-*4] */ -char * -Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, - void **freePtr) + +static char * +ApplyModifiers(char *nstr, const char *tstr, + int startc, int endc, + Var *v, GNode *ctxt, Boolean err, + int *lengthPtr, void **freePtr) { - const char *tstr; /* Pointer into str */ - char *tstr2; /* Secondary tstr if we need - * to expand modifiers */ - Var *v; /* Variable in invocation */ + const char *start; const char *cp; /* Secondary pointer into str (place marker * for tstr) */ - Boolean haveModifier;/* TRUE if have modifiers for the variable */ - char endc; /* Ending character when variable in parens - * or braces */ - char startc=0; /* Starting character when variable in parens - * or braces */ + char *newStr; /* New value to return */ + char termc; /* Character which terminated scan */ int cnt; /* Used to count brace pairs when variable in * in parens or braces */ - int vlen; /* Length of variable name */ - const char *start; - char delim; - char *nstr; - Boolean dynamic; /* TRUE if the variable is local and we're - * expanding it in a non-local context. This - * is done to support dynamic sources. The - * result is just the invocation, unaltered */ + char delim; Var_Parse_State parsestate; /* Flags passed to helper functions */ - *freePtr = NULL; - dynamic = FALSE; - start = str; + delim = '\0'; parsestate.oneBigWord = FALSE; parsestate.varSpace = ' '; /* word separator */ - tstr2 = NULL; - - if (str[1] != PROPEN && str[1] != BROPEN) { - /* - * If it's not bounded by braces of some sort, life is much simpler. - * We just need to check for the first character and return the - * value if it exists. - */ - char name[2]; - - name[0] = str[1]; - name[1] = '\0'; - v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); - if (v == (Var *)NIL) { - *lengthPtr = 2; + start = cp = tstr; + + while (*tstr && *tstr != endc) { - if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) { - /* - * If substituting a local variable in a non-local context, - * assume it's for dynamic source stuff. We have to handle - * this specially and return the longhand for the variable - * with the dollar sign escaped so it makes it back to the - * caller. Only four of the local variables are treated - * specially as they are the only four that will be set - * when dynamic sources are expanded. - */ - switch (str[1]) { - case '@': - return UNCONST("$(.TARGET)"); - case '%': - return UNCONST("$(.ARCHIVE)"); - case '*': - return UNCONST("$(.PREFIX)"); - case '!': - return UNCONST("$(.MEMBER)"); - } - } - /* - * Error + if (*tstr == '$') { + /* + * We have some complex modifiers in a variable. */ - return (err ? var_Error : varNoError); - } else { - haveModifier = FALSE; - tstr = &str[1]; - endc = str[1]; - } - } else if (str[1] == '\0') { - *lengthPtr = 1; - return (err ? var_Error : varNoError); - } else { - Buffer buf; /* Holds the variable name */ + void *freeIt; + char *rval; + int rlen; - startc = str[1]; - endc = startc == PROPEN ? PRCLOSE : BRCLOSE; - buf = Buf_Init(MAKE_BSIZE); + rval = Var_Parse(tstr, ctxt, err, &rlen, &freeIt); - /* - * Skip to the end character or a colon, whichever comes first. - */ - for (tstr = str + 2; - *tstr != '\0' && *tstr != endc && *tstr != ':'; - tstr++) - { - /* - * A variable inside a variable, expand - */ - if (*tstr == '$') { - int rlen; - void *freeIt; - char *rval = Var_Parse(tstr, ctxt, err, &rlen, &freeIt); - if (rval != NULL) { - Buf_AddBytes(buf, strlen(rval), (Byte *)rval); - } - if (freeIt) - free(freeIt); - tstr += rlen - 1; + if (DEBUG(VAR)) { + printf("Got '%s' from '%.*s'%.*s\n", + rval, rlen, tstr, rlen, tstr + rlen); } - else - Buf_AddByte(buf, (Byte)*tstr); - } - if (*tstr == ':') { - haveModifier = TRUE; - } else if (*tstr != '\0') { - haveModifier = FALSE; - } else { - /* - * If we never did find the end character, return NULL - * right now, setting the length to be the distance to - * the end of the string, since that's what make does. - */ - *lengthPtr = tstr - str; - return (var_Error); - } - *WR(tstr) = '\0'; - Buf_AddByte(buf, (Byte)'\0'); - str = Buf_GetAll(buf, NULL); - vlen = strlen(str); - v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); - if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && - (vlen == 2) && (str[1] == 'F' || str[1] == 'D')) - { - /* - * Check for bogus D and F forms of local variables since we're - * in a local context and the name is the right length. - */ - switch(*str) { - case '@': - case '%': - case '*': - case '!': - case '>': - case '<': - { - char vname[2]; - char *val; - - /* - * Well, it's local -- go look for it. - */ - vname[0] = *str; - vname[1] = '\0'; - v = VarFind(vname, ctxt, 0); + tstr += rlen; - if (v != (Var *)NIL) { - /* - * No need for nested expansion or anything, as we're - * the only one who sets these things and we sure don't - * but nested invocations in them... - */ - val = (char *)Buf_GetAll(v->val, NULL); + if (rval != NULL && *rval) { + int used; - if (str[1] == 'D') { - val = VarModify(ctxt, &parsestate, val, VarHead, - (ClientData)0); - } else { - val = VarModify(ctxt, &parsestate, val, VarTail, - (ClientData)0); - } - /* - * Resulting string is dynamically allocated, so - * tell caller to free it. - */ - *freePtr = val; - *lengthPtr = tstr-start+1; - *WR(tstr) = endc; - Buf_Destroy(buf, TRUE); - VarFreeEnv(v, TRUE); - return(val); - } - break; + nstr = ApplyModifiers(nstr, rval, + 0, 0, + v, ctxt, err, &used, freePtr); + if (nstr == var_Error + || (nstr == varNoError && err == 0) + || strlen(rval) != used) { + if (freeIt) + free(freeIt); + goto out; /* error already reported */ } } + if (freeIt) + free(freeIt); + if (*tstr == ':') + tstr++; + continue; } - - if (v == (Var *)NIL) { - if (((vlen == 1) || - (((vlen == 2) && (str[1] == 'F' || - str[1] == 'D')))) && - ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) - { - /* - * If substituting a local variable in a non-local context, - * assume it's for dynamic source stuff. We have to handle - * this specially and return the longhand for the variable - * with the dollar sign escaped so it makes it back to the - * caller. Only four of the local variables are treated - * specially as they are the only four that will be set - * when dynamic sources are expanded. - */ - switch (*str) { - case '@': - case '%': - case '*': - case '!': - dynamic = TRUE; - break; - } - } else if ((vlen > 2) && (*str == '.') && - isupper((unsigned char) str[1]) && - ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + if (DEBUG(VAR)) { + printf("Applying :%c to \"%s\"\n", *tstr, nstr); + } + newStr = var_Error; + switch (*tstr) { + case ':': { - int len; - - len = vlen - 1; - if ((strncmp(str, ".TARGET", len) == 0) || - (strncmp(str, ".ARCHIVE", len) == 0) || - (strncmp(str, ".PREFIX", len) == 0) || - (strncmp(str, ".MEMBER", len) == 0)) - { - dynamic = TRUE; - } - } - - if (!haveModifier) { - /* - * No modifiers -- have specification length so we can return - * now. - */ - *lengthPtr = tstr - start + 1; - *WR(tstr) = endc; - if (dynamic) { - char *pstr = emalloc(*lengthPtr + 1); - strncpy(pstr, start, *lengthPtr); - pstr[*lengthPtr] = '\0'; - *freePtr = pstr; - Buf_Destroy(buf, TRUE); - return(pstr); - } else { - Buf_Destroy(buf, TRUE); - return (err ? var_Error : varNoError); - } - } else { - /* - * Still need to get to the end of the variable specification, - * so kludge up a Var structure for the modifications - */ - v = emalloc(sizeof(Var)); - v->name = UNCONST(str); - v->val = Buf_Init(1); - v->flags = VAR_JUNK; - Buf_Destroy(buf, FALSE); - } - } else - Buf_Destroy(buf, TRUE); - } - - - if (v->flags & VAR_IN_USE) { - Fatal("Variable %s is recursive.", v->name); - /*NOTREACHED*/ - } else { - v->flags |= VAR_IN_USE; - } - /* - * Before doing any modification, we have to make sure the value - * has been fully expanded. If it looks like recursion might be - * necessary (there's a dollar sign somewhere in the variable's value) - * we just call Var_Subst to do any other substitutions that are - * necessary. Note that the value returned by Var_Subst will have - * been dynamically-allocated, so it will need freeing when we - * return. - */ - nstr = (char *)Buf_GetAll(v->val, NULL); - if (strchr(nstr, '$') != NULL) { - nstr = Var_Subst(NULL, nstr, ctxt, err); - *freePtr = nstr; - } - - v->flags &= ~VAR_IN_USE; - - /* - * Now we need to apply any modifiers the user wants applied. - * These are: - * :M words which match the given . - * is of the standard file - * wildcarding form. - * :N words which do not match the given . - * :S[1gW] - * Substitute for in the value - * :C[1gW] - * Substitute for regex in the value - * :H Substitute the head of each word - * :T Substitute the tail of each word - * :E Substitute the extension (minus '.') of - * each word - * :R Substitute the root of each word - * (pathname minus the suffix). - * :O ("Order") Alphabeticaly sort words in variable. - * :Ox ("intermiX") Randomize words in variable. - * :u ("uniq") Remove adjacent duplicate words. - * :tu Converts the variable contents to uppercase. - * :tl Converts the variable contents to lowercase. - * :ts[c] Sets varSpace - the char used to - * separate words to 'c'. If 'c' is - * omitted then no separation is used. - * :tW Treat the variable contents as a single - * word, even if it contains spaces. - * (Mnemonic: one big 'W'ord.) - * :tw Treat the variable contents as multiple - * space-separated words. - * (Mnemonic: many small 'w'ords.) - * :[index] Select a single word from the value. - * :[start..end] Select multiple words from the value. - * :[*] or :[0] Select the entire value, as a single - * word. Equivalent to :tW. - * :[@] Select the entire value, as multiple - * words. Undoes the effect of :[*]. - * Equivalent to :tw. - * :[#] Returns the number of words in the value. - * - * :?: - * If the variable evaluates to true, return - * true value, else return the second value. - * :lhs=rhs Like :S, but the rhs goes to the end of - * the invocation. - * :sh Treat the current value as a command - * to be run, new value is its output. - * The following added so we can handle ODE makefiles. - * :@@@ - * Assign a temporary local variable - * to the current value of each word in turn - * and replace each word with the result of - * evaluating - * :D Use as value if variable defined - * :U Use as value if variable undefined - * :L Use the name of the variable as the value. - * :P Use the path of the node that has the same - * name as the variable as the value. This - * basically includes an implied :L so that - * the common method of refering to the path - * of your dependent 'x' in a rule is to use - * the form '${x:P}'. - * :!! Run cmd much the same as :sh run's the - * current value of the variable. - * The ::= modifiers, actually assign a value to the variable. - * Their main purpose is in supporting modifiers of .for loop - * iterators and other obscure uses. They always expand to - * nothing. In a target rule that would otherwise expand to an - * empty line they can be preceded with @: to keep make happy. - * Eg. - * - * foo: .USE - * .for i in ${.TARGET} ${.TARGET:R}.gz - * @: ${t::=$i} - * @echo blah ${t:T} - * .endfor - * - * ::= Assigns as the new value of variable. - * ::?= Assigns as value of variable if - * it was not already set. - * ::+= Appends to variable. - * ::!= Assigns output of as the new value of - * variable. - */ - if ((nstr != NULL) && haveModifier) { - /* - * Skip initial colon while putting it back. - */ - *WR(tstr) = ':'; - tstr++; - delim = '\0'; - - while (*tstr && *tstr != endc) { - char *newStr; /* New value to return */ - char termc; /* Character which terminated scan */ - - if (*tstr == '$') { - newStr = Var_Subst(NULL, tstr, ctxt, err); - if (tstr2) - free(tstr2); - tstr = tstr2 = newStr; - } - if (DEBUG(VAR)) { - printf("Applying :%c to \"%s\"\n", *tstr, nstr); - } - newStr = var_Error; - switch (*tstr) { - case ':': - { if (tstr[1] == '=' || (tstr[2] == '=' && (tstr[1] == '!' || tstr[1] == '+' || tstr[1] == '?'))) { @@ -2344,9 +2076,9 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, pattern.flags = 0; pattern.rhs = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, NULL, - &pattern.rightLen, - NULL); + &cp, delim, NULL, + &pattern.rightLen, + NULL); if (v->flags & VAR_JUNK) { /* restore original name */ free(v->name); @@ -2367,7 +2099,7 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, if (emsg) Error(emsg, nstr); else - Var_Set(v->name, newStr, v_ctxt, 0); + Var_Set(v->name, newStr, v_ctxt, 0); if (newStr) free(newStr); break; @@ -2384,59 +2116,59 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, break; } goto default_case; /* "::" */ - } - case '@': - { - VarLoop_t loop; - int flags = VAR_NOSUBST; - - cp = ++tstr; - delim = '@'; - if ((loop.tvar = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, - &flags, &loop.tvarLen, - NULL)) == NULL) - goto cleanup; - - if ((loop.str = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, - &flags, &loop.strLen, - NULL)) == NULL) - goto cleanup; - - termc = *cp; - delim = '\0'; - - loop.err = err; - loop.ctxt = ctxt; - newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand, - (ClientData)&loop); - free(loop.tvar); - free(loop.str); - break; - } - case 'D': - case 'U': - { - Buffer buf; /* Buffer for patterns */ - int wantit; /* want data in buffer */ + } + case '@': + { + VarLoop_t loop; + int flags = VAR_NOSUBST; + + cp = ++tstr; + delim = '@'; + if ((loop.tvar = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, + &flags, &loop.tvarLen, + NULL)) == NULL) + goto cleanup; + + if ((loop.str = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, + &flags, &loop.strLen, + NULL)) == NULL) + goto cleanup; + + termc = *cp; + delim = '\0'; + + loop.err = err; + loop.ctxt = ctxt; + newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand, + (ClientData)&loop); + free(loop.tvar); + free(loop.str); + break; + } + case 'D': + case 'U': + { + Buffer buf; /* Buffer for patterns */ + int wantit; /* want data in buffer */ - /* - * Pass through tstr looking for 1) escaped delimiters, - * '$'s and backslashes (place the escaped character in - * uninterpreted) and 2) unescaped $'s that aren't before - * the delimiter (expand the variable substitution). - * The result is left in the Buffer buf. - */ - buf = Buf_Init(0); - for (cp = tstr + 1; - *cp != endc && *cp != ':' && *cp != '\0'; - cp++) { - if ((*cp == '\\') && - ((cp[1] == ':') || - (cp[1] == '$') || - (cp[1] == endc) || - (cp[1] == '\\'))) + /* + * Pass through tstr looking for 1) escaped delimiters, + * '$'s and backslashes (place the escaped character in + * uninterpreted) and 2) unescaped $'s that aren't before + * the delimiter (expand the variable substitution). + * The result is left in the Buffer buf. + */ + buf = Buf_Init(0); + for (cp = tstr + 1; + *cp != endc && *cp != ':' && *cp != '\0'; + cp++) { + if ((*cp == '\\') && + ((cp[1] == ':') || + (cp[1] == '$') || + (cp[1] == endc) || + (cp[1] == '\\'))) { Buf_AddByte(buf, (Byte)cp[1]); cp++; @@ -2457,343 +2189,343 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, } else { Buf_AddByte(buf, (Byte)*cp); } - } - Buf_AddByte(buf, (Byte)'\0'); + } + Buf_AddByte(buf, (Byte)'\0'); - termc = *cp; + termc = *cp; - if (*tstr == 'U') - wantit = ((v->flags & VAR_JUNK) != 0); - else - wantit = ((v->flags & VAR_JUNK) == 0); - if ((v->flags & VAR_JUNK) != 0) - v->flags |= VAR_KEEP; - if (wantit) { - newStr = (char *)Buf_GetAll(buf, NULL); - Buf_Destroy(buf, FALSE); - } else { - newStr = nstr; - Buf_Destroy(buf, TRUE); - } - break; + if (*tstr == 'U') + wantit = ((v->flags & VAR_JUNK) != 0); + else + wantit = ((v->flags & VAR_JUNK) == 0); + if ((v->flags & VAR_JUNK) != 0) + v->flags |= VAR_KEEP; + if (wantit) { + newStr = (char *)Buf_GetAll(buf, NULL); + Buf_Destroy(buf, FALSE); + } else { + newStr = nstr; + Buf_Destroy(buf, TRUE); } - case 'L': - { - if ((v->flags & VAR_JUNK) != 0) - v->flags |= VAR_KEEP; + break; + } + case 'L': + { + if ((v->flags & VAR_JUNK) != 0) + v->flags |= VAR_KEEP; + newStr = strdup(v->name); + cp = ++tstr; + termc = *tstr; + break; + } + case 'P': + { + GNode *gn; + + if ((v->flags & VAR_JUNK) != 0) + v->flags |= VAR_KEEP; + gn = Targ_FindNode(v->name, TARG_NOCREATE); + if (gn == NILGNODE || gn->type & OP_NOPATH) { + newStr = NULL; + } else if (gn->path) { + newStr = strdup(gn->path); + } else { + newStr = Dir_FindFile(v->name, Suff_FindPath(gn)); + } + if (!newStr) { newStr = strdup(v->name); - cp = ++tstr; - termc = *tstr; - break; } - case 'P': - { - GNode *gn; + cp = ++tstr; + termc = *tstr; + break; + } + case '!': + { + const char *emsg; + VarPattern pattern; + pattern.flags = 0; + + delim = '!'; + + cp = ++tstr; + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, + NULL, &pattern.rightLen, + NULL)) == NULL) + goto cleanup; + newStr = Cmd_Exec(pattern.rhs, &emsg); + free(UNCONST(pattern.rhs)); + if (emsg) + Error(emsg, nstr); + termc = *cp; + delim = '\0'; + if (v->flags & VAR_JUNK) { + v->flags |= VAR_KEEP; + } + break; + } + case '[': + { + /* + * Look for the closing ']', recursively + * expanding any embedded variables. + * + * estr is a pointer to the expanded result, + * which we must free(). + */ + char *estr; - if ((v->flags & VAR_JUNK) != 0) - v->flags |= VAR_KEEP; - gn = Targ_FindNode(v->name, TARG_NOCREATE); - if (gn == NILGNODE || gn->type & OP_NOPATH) { - newStr = NULL; - } else if (gn->path) { - newStr = strdup(gn->path); - } else { - newStr = Dir_FindFile(v->name, Suff_FindPath(gn)); - } - if (!newStr) { - newStr = strdup(v->name); - } - cp = ++tstr; - termc = *tstr; - break; + cp = tstr+1; /* point to char after '[' */ + delim = ']'; /* look for closing ']' */ + estr = VarGetPattern(ctxt, &parsestate, + err, &cp, delim, + NULL, NULL, NULL); + if (estr == NULL) + goto cleanup; /* report missing ']' */ + /* now cp points just after the closing ']' */ + delim = '\0'; + if (cp[0] != ':' && cp[0] != endc) { + /* Found junk after ']' */ + free(estr); + goto bad_modifier; } - case '!': - { - const char *emsg; - VarPattern pattern; - pattern.flags = 0; + if (estr[0] == '\0') { + /* Found empty square brackets in ":[]". */ + free(estr); + goto bad_modifier; + } else if (estr[0] == '#' && estr[1] == '\0') { + /* Found ":[#]" */ - delim = '!'; + /* + * We will need enough space for the decimal + * representation of an int. We calculate the + * space needed for the octal representation, + * and add enough slop to cope with a '-' sign + * (which should never be needed) and a '\0' + * string terminator. + */ + int newStrSize = + (sizeof(int) * CHAR_BIT + 2) / 3 + 2; - cp = ++tstr; - if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, - NULL, &pattern.rightLen, - NULL)) == NULL) - goto cleanup; - newStr = Cmd_Exec(pattern.rhs, &emsg); - free(UNCONST(pattern.rhs)); - if (emsg) - Error(emsg, nstr); - termc = *cp; - delim = '\0'; - if (v->flags & VAR_JUNK) { - v->flags |= VAR_KEEP; + newStr = emalloc(newStrSize); + if (parsestate.oneBigWord) { + strncpy(newStr, "1", newStrSize); + } else { + /* XXX: brk_string() is a rather expensive + * way of counting words. */ + char **av; + char *as; + int ac; + + av = brk_string(nstr, &ac, FALSE, &as); + snprintf(newStr, newStrSize, "%d", ac); + free(as); + free(av); } + termc = *cp; + free(estr); break; - } - case '[': - { + } else if (estr[0] == '*' && estr[1] == '\0') { + /* Found ":[*]" */ + parsestate.oneBigWord = TRUE; + newStr = nstr; + termc = *cp; + free(estr); + break; + } else if (estr[0] == '@' && estr[1] == '\0') { + /* Found ":[@]" */ + parsestate.oneBigWord = FALSE; + newStr = nstr; + termc = *cp; + free(estr); + break; + } else { /* - * Look for the closing ']', recursively - * expanding any embedded variables. - * - * estr is a pointer to the expanded result, - * which we must free(). + * We expect estr to contain a single + * integer for :[N], or two integers + * separated by ".." for :[start..end]. */ - char *estr; - - cp = tstr+1; /* point to char after '[' */ - delim = ']'; /* look for closing ']' */ - estr = VarGetPattern(ctxt, &parsestate, - err, &cp, delim, - NULL, NULL, NULL); - if (estr == NULL) - goto cleanup; /* report missing ']' */ - /* now cp points just after the closing ']' */ - delim = '\0'; - if (cp[0] != ':' && cp[0] != endc) { - /* Found junk after ']' */ - free(estr); - goto bad_modifier; - } - if (estr[0] == '\0') { - /* Found empty square brackets in ":[]". */ - free(estr); - goto bad_modifier; - } else if (estr[0] == '#' && estr[1] == '\0') { - /* Found ":[#]" */ + char *ep; - /* - * We will need enough space for the decimal - * representation of an int. We calculate the - * space needed for the octal representation, - * and add enough slop to cope with a '-' sign - * (which should never be needed) and a '\0' - * string terminator. - */ - int newStrSize = - (sizeof(int) * CHAR_BIT + 2) / 3 + 2; + VarSelectWords_t seldata = { 0, 0 }; - newStr = emalloc(newStrSize); - if (parsestate.oneBigWord) { - strncpy(newStr, "1", newStrSize); - } else { - /* XXX: brk_string() is a rather expensive - * way of counting words. */ - char **av; - char *as; - int ac; - - av = brk_string(nstr, &ac, FALSE, &as); - snprintf(newStr, newStrSize, "%d", ac); - free(as); - free(av); + seldata.start = strtol(estr, &ep, 0); + if (ep == estr) { + /* Found junk instead of a number */ + free(estr); + goto bad_modifier; + } else if (ep[0] == '\0') { + /* Found only one integer in :[N] */ + seldata.end = seldata.start; + } else if (ep[0] == '.' && ep[1] == '.' && + ep[2] != '\0') { + /* Expecting another integer after ".." */ + ep += 2; + seldata.end = strtol(ep, &ep, 0); + if (ep[0] != '\0') { + /* Found junk after ".." */ + free(estr); + goto bad_modifier; } - termc = *cp; + } else { + /* Found junk instead of ".." */ free(estr); - break; - } else if (estr[0] == '*' && estr[1] == '\0') { - /* Found ":[*]" */ + goto bad_modifier; + } + /* + * Now seldata is properly filled in, + * but we still have to check for 0 as + * a special case. + */ + if (seldata.start == 0 && seldata.end == 0) { + /* ":[0]" or perhaps ":[0..0]" */ parsestate.oneBigWord = TRUE; newStr = nstr; termc = *cp; free(estr); break; - } else if (estr[0] == '@' && estr[1] == '\0') { - /* Found ":[@]" */ - parsestate.oneBigWord = FALSE; - newStr = nstr; - termc = *cp; + } else if (seldata.start == 0 || + seldata.end == 0) { + /* ":[0..N]" or ":[N..0]" */ free(estr); - break; - } else { - /* - * We expect estr to contain a single - * integer for :[N], or two integers - * separated by ".." for :[start..end]. - */ - char *ep; + goto bad_modifier; + } + /* + * Normal case: select the words + * described by seldata. + */ + newStr = VarSelectWords(ctxt, &parsestate, + nstr, &seldata); - VarSelectWords_t seldata = { 0, 0 }; + termc = *cp; + free(estr); + break; + } - seldata.start = strtol(estr, &ep, 0); - if (ep == estr) { - /* Found junk instead of a number */ - free(estr); - goto bad_modifier; - } else if (ep[0] == '\0') { - /* Found only one integer in :[N] */ - seldata.end = seldata.start; - } else if (ep[0] == '.' && ep[1] == '.' && - ep[2] != '\0') { - /* Expecting another integer after ".." */ - ep += 2; - seldata.end = strtol(ep, &ep, 0); - if (ep[0] != '\0') { - /* Found junk after ".." */ - free(estr); - goto bad_modifier; - } - } else { - /* Found junk instead of ".." */ - free(estr); - goto bad_modifier; - } - /* - * Now seldata is properly filled in, - * but we still have to check for 0 as - * a special case. - */ - if (seldata.start == 0 && seldata.end == 0) { - /* ":[0]" or perhaps ":[0..0]" */ - parsestate.oneBigWord = TRUE; - newStr = nstr; - termc = *cp; - free(estr); - break; - } else if (seldata.start == 0 || - seldata.end == 0) { - /* ":[0..N]" or ":[N..0]" */ - free(estr); - goto bad_modifier; - } + } + case 't': + { + cp = tstr + 1; /* make sure it is set */ + if (tstr[1] != endc && tstr[1] != ':') { + if (tstr[1] == 's') { /* - * Normal case: select the words - * described by seldata. + * Use the char (if any) at tstr[2] + * as the word separator. */ - newStr = VarSelectWords(ctxt, &parsestate, - nstr, &seldata); - - termc = *cp; - free(estr); - break; - } - - } - case 't': - { - cp = tstr + 1; /* make sure it is set */ - if (tstr[1] != endc && tstr[1] != ':') { - if (tstr[1] == 's') { - /* - * Use the char (if any) at tstr[2] - * as the word separator. - */ - VarPattern pattern; - - if (tstr[2] != endc && - (tstr[3] == endc || tstr[3] == ':')) { - /* ":ts" or - * ":ts:" */ - parsestate.varSpace = tstr[2]; - cp = tstr + 3; - } else if (tstr[2] == endc || tstr[2] == ':') { - /* ":ts" or ":ts:" */ - parsestate.varSpace = 0; /* no separator */ - cp = tstr + 2; - } else if (tstr[2] == '\\') { - switch (tstr[3]) { - case 'n': - parsestate.varSpace = '\n'; - cp = tstr + 4; - break; - case 't': - parsestate.varSpace = '\t'; - cp = tstr + 4; - break; - default: - if (isdigit((unsigned char)tstr[3])) { - char *ep; + VarPattern pattern; + + if (tstr[2] != endc && + (tstr[3] == endc || tstr[3] == ':')) { + /* ":ts" or + * ":ts:" */ + parsestate.varSpace = tstr[2]; + cp = tstr + 3; + } else if (tstr[2] == endc || tstr[2] == ':') { + /* ":ts" or ":ts:" */ + parsestate.varSpace = 0; /* no separator */ + cp = tstr + 2; + } else if (tstr[2] == '\\') { + switch (tstr[3]) { + case 'n': + parsestate.varSpace = '\n'; + cp = tstr + 4; + break; + case 't': + parsestate.varSpace = '\t'; + cp = tstr + 4; + break; + default: + if (isdigit((unsigned char)tstr[3])) { + char *ep; - parsestate.varSpace = - strtoul(&tstr[3], &ep, 0); - if (*ep != ':' && *ep != endc) - goto bad_modifier; - cp = ep; - } else { - /* - * ":ts". - */ + parsestate.varSpace = + strtoul(&tstr[3], &ep, 0); + if (*ep != ':' && *ep != endc) goto bad_modifier; - } - break; + cp = ep; + } else { + /* + * ":ts". + */ + goto bad_modifier; } - } else { - /* - * Found ":ts". - */ - goto bad_modifier; + break; } - - termc = *cp; - + } else { /* - * We cannot be certain that VarModify - * will be used - even if there is a - * subsequent modifier, so do a no-op - * VarSubstitute now to for str to be - * re-expanded without the spaces. + * Found ":ts". */ - pattern.flags = VAR_SUB_ONE; - pattern.lhs = pattern.rhs = "\032"; - pattern.leftLen = pattern.rightLen = 1; + goto bad_modifier; + } - newStr = VarModify(ctxt, &parsestate, nstr, - VarSubstitute, - (ClientData)&pattern); - } else if (tstr[2] == endc || tstr[2] == ':') { - /* - * Check for two-character options: - * ":tu", ":tl" - */ - if (tstr[1] == 'u' || tstr[1] == 'l') { - newStr = VarChangeCase(nstr, (tstr[1] == 'u')); - cp = tstr + 2; - termc = *cp; - } else if (tstr[1] == 'W' || tstr[1] == 'w') { - parsestate.oneBigWord = (tstr[1] == 'W'); - newStr = nstr; - cp = tstr + 2; - termc = *cp; - } else { - /* Found ":t:" or - * ":t". */ - goto bad_modifier; - } + termc = *cp; + + /* + * We cannot be certain that VarModify + * will be used - even if there is a + * subsequent modifier, so do a no-op + * VarSubstitute now to for str to be + * re-expanded without the spaces. + */ + pattern.flags = VAR_SUB_ONE; + pattern.lhs = pattern.rhs = "\032"; + pattern.leftLen = pattern.rightLen = 1; + + newStr = VarModify(ctxt, &parsestate, nstr, + VarSubstitute, + (ClientData)&pattern); + } else if (tstr[2] == endc || tstr[2] == ':') { + /* + * Check for two-character options: + * ":tu", ":tl" + */ + if (tstr[1] == 'u' || tstr[1] == 'l') { + newStr = VarChangeCase(nstr, (tstr[1] == 'u')); + cp = tstr + 2; + termc = *cp; + } else if (tstr[1] == 'W' || tstr[1] == 'w') { + parsestate.oneBigWord = (tstr[1] == 'W'); + newStr = nstr; + cp = tstr + 2; + termc = *cp; } else { - /* - * Found ":t". - */ + /* Found ":t:" or + * ":t". */ goto bad_modifier; } } else { /* - * Found ":t" or ":t:". + * Found ":t". */ goto bad_modifier; } - break; - } - case 'N': - case 'M': - { - char *pattern; - char *cp2; - Boolean copy; - int nest; - - copy = FALSE; - nest = 1; + } else { /* - * In the loop below, ignore ':' unless we are at - * (or back to) the original brace level. - * XXX This will likely not work right if $() and ${} - * are intermixed. + * Found ":t" or ":t:". */ - for (cp = tstr + 1; - *cp != '\0' && !(*cp == ':' && nest == 1); - cp++) + goto bad_modifier; + } + break; + } + case 'N': + case 'M': + { + char *pattern; + char *cp2; + Boolean copy; + int nest; + + copy = FALSE; + nest = 1; + /* + * In the loop below, ignore ':' unless we are at + * (or back to) the original brace level. + * XXX This will likely not work right if $() and ${} + * are intermixed. + */ + for (cp = tstr + 1; + *cp != '\0' && !(*cp == ':' && nest == 1); + cp++) { if (*cp == '\\' && (cp[1] == ':' || @@ -2808,416 +2540,751 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, --nest; if (nest == 0) break; - } - } - termc = *cp; - *WR(cp) = '\0'; - if (copy) { - /* - * Need to compress the \:'s out of the pattern, so - * allocate enough room to hold the uncompressed - * pattern (note that cp started at tstr+1, so - * cp - tstr takes the null byte into account) and - * compress the pattern into the space. - */ - pattern = emalloc(cp - tstr); - for (cp2 = pattern, cp = tstr + 1; - *cp != '\0'; - cp++, cp2++) + } + } + termc = *cp; + *WR(cp) = '\0'; + if (copy) { + /* + * Need to compress the \:'s out of the pattern, so + * allocate enough room to hold the uncompressed + * pattern (note that cp started at tstr+1, so + * cp - tstr takes the null byte into account) and + * compress the pattern into the space. + */ + pattern = emalloc(cp - tstr); + for (cp2 = pattern, cp = tstr + 1; + *cp != '\0'; + cp++, cp2++) { if ((*cp == '\\') && (cp[1] == ':' || cp[1] == endc)) { - cp++; + cp++; } *cp2 = *cp; } - *cp2 = '\0'; - } else { - pattern = UNCONST(&tstr[1]); - } - if ((cp2 = strchr(pattern, '$'))) { - cp2 = pattern; - pattern = Var_Subst(NULL, cp2, ctxt, err); - if (copy) - free(cp2); - copy = TRUE; - } - if (*tstr == 'M' || *tstr == 'm') { - newStr = VarModify(ctxt, &parsestate, nstr, VarMatch, - (ClientData)pattern); - } else { - newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch, - (ClientData)pattern); - } - if (copy) { - free(pattern); - } - break; + *cp2 = '\0'; + } else { + pattern = UNCONST(&tstr[1]); } - case 'S': - { - VarPattern pattern; - Var_Parse_State tmpparsestate; + if ((cp2 = strchr(pattern, '$'))) { + cp2 = pattern; + pattern = Var_Subst(NULL, cp2, ctxt, err); + if (copy) + free(cp2); + copy = TRUE; + } + if (*tstr == 'M' || *tstr == 'm') { + newStr = VarModify(ctxt, &parsestate, nstr, VarMatch, + (ClientData)pattern); + } else { + newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch, + (ClientData)pattern); + } + if (copy) { + free(pattern); + } + break; + } + case 'S': + { + VarPattern pattern; + Var_Parse_State tmpparsestate; - pattern.flags = 0; - tmpparsestate = parsestate; - delim = tstr[1]; - tstr += 2; + pattern.flags = 0; + tmpparsestate = parsestate; + delim = tstr[1]; + tstr += 2; - /* - * If pattern begins with '^', it is anchored to the - * start of the word -- skip over it and flag pattern. - */ - if (*tstr == '^') { - pattern.flags |= VAR_MATCH_START; - tstr += 1; - } + /* + * If pattern begins with '^', it is anchored to the + * start of the word -- skip over it and flag pattern. + */ + if (*tstr == '^') { + pattern.flags |= VAR_MATCH_START; + tstr += 1; + } - cp = tstr; - if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, - &pattern.flags, - &pattern.leftLen, - NULL)) == NULL) - goto cleanup; + cp = tstr; + if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, + &pattern.flags, + &pattern.leftLen, + NULL)) == NULL) + goto cleanup; - if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, NULL, - &pattern.rightLen, - &pattern)) == NULL) - goto cleanup; + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, NULL, + &pattern.rightLen, + &pattern)) == NULL) + goto cleanup; - /* - * Check for global substitution. If 'g' after the final - * delimiter, substitution is global and is marked that - * way. - */ - for (;; cp++) { - switch (*cp) { - case 'g': - pattern.flags |= VAR_SUB_GLOBAL; - continue; - case '1': - pattern.flags |= VAR_SUB_ONE; - continue; - case 'W': - tmpparsestate.oneBigWord = TRUE; - continue; - } - break; + /* + * Check for global substitution. If 'g' after the final + * delimiter, substitution is global and is marked that + * way. + */ + for (;; cp++) { + switch (*cp) { + case 'g': + pattern.flags |= VAR_SUB_GLOBAL; + continue; + case '1': + pattern.flags |= VAR_SUB_ONE; + continue; + case 'W': + tmpparsestate.oneBigWord = TRUE; + continue; } + break; + } - termc = *cp; - newStr = VarModify(ctxt, &tmpparsestate, nstr, - VarSubstitute, - (ClientData)&pattern); + termc = *cp; + newStr = VarModify(ctxt, &tmpparsestate, nstr, + VarSubstitute, + (ClientData)&pattern); - /* - * Free the two strings. - */ - free(UNCONST(pattern.lhs)); - free(UNCONST(pattern.rhs)); - delim = '\0'; - break; - } - case '?': - { - VarPattern pattern; - Boolean value; + /* + * Free the two strings. + */ + free(UNCONST(pattern.lhs)); + free(UNCONST(pattern.rhs)); + delim = '\0'; + break; + } + case '?': + { + VarPattern pattern; + Boolean value; + + /* find ':', and then substitute accordingly */ + + pattern.flags = 0; + + cp = ++tstr; + delim = ':'; + if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, NULL, + &pattern.leftLen, + NULL)) == NULL) + goto cleanup; + + /* BROPEN or PROPEN */ + delim = endc; + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err, + &cp, delim, NULL, + &pattern.rightLen, + NULL)) == NULL) + goto cleanup; + + termc = *--cp; + delim = '\0'; + if (Cond_EvalExpression(1, v->name, &value, 0) + == COND_INVALID) { + Error("Bad conditional expression `%s' in %s?%s:%s", + v->name, v->name, pattern.lhs, pattern.rhs); + goto cleanup; + } - /* find ':', and then substitute accordingly */ + if (value) { + newStr = UNCONST(pattern.lhs); + free(UNCONST(pattern.rhs)); + } else { + newStr = UNCONST(pattern.rhs); + free(UNCONST(pattern.lhs)); + } + if (v->flags & VAR_JUNK) { + v->flags |= VAR_KEEP; + } + break; + } +#ifndef NO_REGEX + case 'C': + { + VarREPattern pattern; + char *re; + int error; + Var_Parse_State tmpparsestate; - pattern.flags = 0; + pattern.flags = 0; + tmpparsestate = parsestate; + delim = tstr[1]; + tstr += 2; - cp = ++tstr; - delim = ':'; - if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, NULL, - &pattern.leftLen, - NULL)) == NULL) - goto cleanup; + cp = tstr; - /* BROPEN or PROPEN */ - delim = endc; - if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, err, - &cp, delim, NULL, - &pattern.rightLen, - NULL)) == NULL) - goto cleanup; + if ((re = VarGetPattern(ctxt, &parsestate, err, &cp, delim, + NULL, NULL, NULL)) == NULL) + goto cleanup; - termc = *--cp; - delim = '\0'; - if (Cond_EvalExpression(1, v->name, &value, 0) - == COND_INVALID) { - Error("Bad conditional expression `%s' in %s?%s:%s", - v->name, v->name, pattern.lhs, pattern.rhs); - goto cleanup; - } + if ((pattern.replace = VarGetPattern(ctxt, &parsestate, + err, &cp, delim, NULL, + NULL, NULL)) == NULL){ + free(re); + goto cleanup; + } - if (value) { - newStr = UNCONST(pattern.lhs); - free(UNCONST(pattern.rhs)); - } else { - newStr = UNCONST(pattern.rhs); - free(UNCONST(pattern.lhs)); - } - if (v->flags & VAR_JUNK) { - v->flags |= VAR_KEEP; + for (;; cp++) { + switch (*cp) { + case 'g': + pattern.flags |= VAR_SUB_GLOBAL; + continue; + case '1': + pattern.flags |= VAR_SUB_ONE; + continue; + case 'W': + tmpparsestate.oneBigWord = TRUE; + continue; } break; } -#ifndef NO_REGEX - case 'C': - { - VarREPattern pattern; - char *re; - int error; - Var_Parse_State tmpparsestate; - pattern.flags = 0; - tmpparsestate = parsestate; - delim = tstr[1]; - tstr += 2; + termc = *cp; - cp = tstr; + error = regcomp(&pattern.re, re, REG_EXTENDED); + free(re); + if (error) { + *lengthPtr = cp - start + 1; + VarREError(error, &pattern.re, "RE substitution error"); + free(pattern.replace); + goto cleanup; + } - if ((re = VarGetPattern(ctxt, &parsestate, err, &cp, delim, - NULL, NULL, NULL)) == NULL) - goto cleanup; + pattern.nsub = pattern.re.re_nsub + 1; + if (pattern.nsub < 1) + pattern.nsub = 1; + if (pattern.nsub > 10) + pattern.nsub = 10; + pattern.matches = emalloc(pattern.nsub * + sizeof(regmatch_t)); + newStr = VarModify(ctxt, &tmpparsestate, nstr, + VarRESubstitute, + (ClientData) &pattern); + regfree(&pattern.re); + free(pattern.replace); + free(pattern.matches); + delim = '\0'; + break; + } +#endif + case 'Q': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarQuote(nstr); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'T': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarTail, + (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'H': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarHead, + (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'E': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix, + (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'R': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarRoot, + (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'O': + { + char otype; - if ((pattern.replace = VarGetPattern(ctxt, &parsestate, - err, &cp, delim, NULL, - NULL, NULL)) == NULL){ - free(re); - goto cleanup; - } + cp = tstr + 1; /* skip to the rest in any case */ + if (tstr[1] == endc || tstr[1] == ':') { + otype = 's'; + termc = *cp; + } else if ( (tstr[1] == 'x') && + (tstr[2] == endc || tstr[2] == ':') ) { + otype = tstr[1]; + cp = tstr + 2; + termc = *cp; + } else { + goto bad_modifier; + } + newStr = VarOrder(nstr, otype); + break; + } + case 'u': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarUniq(nstr); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; +#ifdef SUNSHCMD + case 's': + if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) { + const char *emsg; + newStr = Cmd_Exec(nstr, &emsg); + if (emsg) + Error(emsg, nstr); + cp = tstr + 2; + termc = *cp; + break; + } + goto default_case; +#endif + default: + default_case: + { +#ifdef SYSVVARSUB + /* + * This can either be a bogus modifier or a System-V + * substitution command. + */ + VarPattern pattern; + Boolean eqFound; - for (;; cp++) { - switch (*cp) { - case 'g': - pattern.flags |= VAR_SUB_GLOBAL; - continue; - case '1': - pattern.flags |= VAR_SUB_ONE; - continue; - case 'W': - tmpparsestate.oneBigWord = TRUE; - continue; - } - break; - } + pattern.flags = 0; + eqFound = FALSE; + /* + * First we make a pass through the string trying + * to verify it is a SYSV-make-style translation: + * it must be: =) + */ + cp = tstr; + cnt = 1; + while (*cp != '\0' && cnt) { + if (*cp == '=') { + eqFound = TRUE; + /* continue looking for endc */ + } + else if (*cp == endc) + cnt--; + else if (*cp == startc) + cnt++; + if (cnt) + cp++; + } + if (*cp == endc && eqFound) { + + /* + * Now we break this sucker into the lhs and + * rhs. We must null terminate them of course. + */ + delim='='; + cp = tstr; + if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, + err, &cp, delim, &pattern.flags, + &pattern.leftLen, NULL)) == NULL) + goto cleanup; + delim = endc; + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, + err, &cp, delim, NULL, &pattern.rightLen, + &pattern)) == NULL) + goto cleanup; + /* + * SYSV modifications happen through the whole + * string. Note the pattern is anchored at the end. + */ + termc = *--cp; + delim = '\0'; + newStr = VarModify(ctxt, &parsestate, nstr, + VarSYSVMatch, + (ClientData)&pattern); + free(UNCONST(pattern.lhs)); + free(UNCONST(pattern.rhs)); + } else +#endif + { + Error("Unknown modifier '%c'", *tstr); + for (cp = tstr+1; + *cp != ':' && *cp != endc && *cp != '\0'; + cp++) + continue; termc = *cp; + newStr = var_Error; + } + } + } + if (DEBUG(VAR)) { + printf("Result is \"%s\"\n", newStr); + } - error = regcomp(&pattern.re, re, REG_EXTENDED); - free(re); - if (error) { - *lengthPtr = cp - start + 1; - VarREError(error, &pattern.re, "RE substitution error"); - free(pattern.replace); - goto cleanup; - } + if (newStr != nstr) { + if (*freePtr) { + free(nstr); + *freePtr = NULL; + } + nstr = newStr; + if (nstr != var_Error && nstr != varNoError) { + *freePtr = nstr; + } + } + if (termc == '\0' && endc != '\0') { + Error("Unclosed variable specification for %s", v->name); + } else if (termc == ':') { + *WR(cp) = termc; + cp++; + } else { + *WR(cp) = termc; + } + tstr = cp; + } + out: + *lengthPtr = tstr - start; + return (nstr); + + bad_modifier: + /* "{(" */ + Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr, + v->name); + + cleanup: + *lengthPtr = cp - start; + if (delim != '\0') + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + if (*freePtr) { + free(*freePtr); + *freePtr = NULL; + } + return (var_Error); +} + +/*- + *----------------------------------------------------------------------- + * Var_Parse -- + * Given the start of a variable invocation, extract the variable + * name and find its value, then modify it according to the + * specification. + * + * Input: + * str The string to parse + * ctxt The context for the variable + * err TRUE if undefined variables are an error + * lengthPtr OUT: The length of the specification + * freePtr OUT: TRUE if caller should free result + * + * Results: + * The (possibly-modified) value of the variable or var_Error if the + * specification is invalid. The length of the specification is + * placed in *lengthPtr (for invalid specifications, this is just + * 2...?). + * A Boolean in *freePtr telling whether the returned string should + * be freed by the caller. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +/* coverity[+alloc : arg-*4] */ +char * +Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, + void **freePtr) +{ + const char *tstr; /* Pointer into str */ + Var *v; /* Variable in invocation */ + Boolean haveModifier;/* TRUE if have modifiers for the variable */ + char endc; /* Ending character when variable in parens + * or braces */ + char startc=0; /* Starting character when variable in parens + * or braces */ + int vlen; /* Length of variable name */ + const char *start; + char *nstr; + Boolean dynamic; /* TRUE if the variable is local and we're + * expanding it in a non-local context. This + * is done to support dynamic sources. The + * result is just the invocation, unaltered */ + Var_Parse_State parsestate; /* Flags passed to helper functions */ + + *freePtr = NULL; + dynamic = FALSE; + start = str; + parsestate.oneBigWord = FALSE; + parsestate.varSpace = ' '; /* word separator */ + + if (str[1] != PROPEN && str[1] != BROPEN) { + /* + * If it's not bounded by braces of some sort, life is much simpler. + * We just need to check for the first character and return the + * value if it exists. + */ + char name[2]; + + name[0] = str[1]; + name[1] = '\0'; + + v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if (v == (Var *)NIL) { + *lengthPtr = 2; + + if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (str[1]) { + case '@': + return UNCONST("$(.TARGET)"); + case '%': + return UNCONST("$(.ARCHIVE)"); + case '*': + return UNCONST("$(.PREFIX)"); + case '!': + return UNCONST("$(.MEMBER)"); + } + } + /* + * Error + */ + return (err ? var_Error : varNoError); + } else { + haveModifier = FALSE; + tstr = &str[1]; + endc = str[1]; + } + } else if (str[1] == '\0') { + *lengthPtr = 1; + return (err ? var_Error : varNoError); + } else { + Buffer buf; /* Holds the variable name */ - pattern.nsub = pattern.re.re_nsub + 1; - if (pattern.nsub < 1) - pattern.nsub = 1; - if (pattern.nsub > 10) - pattern.nsub = 10; - pattern.matches = emalloc(pattern.nsub * - sizeof(regmatch_t)); - newStr = VarModify(ctxt, &tmpparsestate, nstr, - VarRESubstitute, - (ClientData) &pattern); - regfree(&pattern.re); - free(pattern.replace); - free(pattern.matches); - delim = '\0'; - break; - } -#endif - case 'Q': - if (tstr[1] == endc || tstr[1] == ':') { - newStr = VarQuote(nstr); - cp = tstr + 1; - termc = *cp; - break; - } - goto default_case; - case 'T': - if (tstr[1] == endc || tstr[1] == ':') { - newStr = VarModify(ctxt, &parsestate, nstr, VarTail, - (ClientData)0); - cp = tstr + 1; - termc = *cp; - break; - } - goto default_case; - case 'H': - if (tstr[1] == endc || tstr[1] == ':') { - newStr = VarModify(ctxt, &parsestate, nstr, VarHead, - (ClientData)0); - cp = tstr + 1; - termc = *cp; - break; - } - goto default_case; - case 'E': - if (tstr[1] == endc || tstr[1] == ':') { - newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix, - (ClientData)0); - cp = tstr + 1; - termc = *cp; - break; - } - goto default_case; - case 'R': - if (tstr[1] == endc || tstr[1] == ':') { - newStr = VarModify(ctxt, &parsestate, nstr, VarRoot, - (ClientData)0); - cp = tstr + 1; - termc = *cp; - break; - } - goto default_case; - case 'O': - { - char otype; + startc = str[1]; + endc = startc == PROPEN ? PRCLOSE : BRCLOSE; + buf = Buf_Init(MAKE_BSIZE); - cp = tstr + 1; /* skip to the rest in any case */ - if (tstr[1] == endc || tstr[1] == ':') { - otype = 's'; - termc = *cp; - } else if ( (tstr[1] == 'x') && - (tstr[2] == endc || tstr[2] == ':') ) { - otype = tstr[1]; - cp = tstr + 2; - termc = *cp; - } else { - goto bad_modifier; - } - newStr = VarOrder(nstr, otype); - break; + /* + * Skip to the end character or a colon, whichever comes first. + */ + for (tstr = str + 2; + *tstr != '\0' && *tstr != endc && *tstr != ':'; + tstr++) + { + /* + * A variable inside a variable, expand + */ + if (*tstr == '$') { + int rlen; + void *freeIt; + char *rval = Var_Parse(tstr, ctxt, err, &rlen, &freeIt); + if (rval != NULL) { + Buf_AddBytes(buf, strlen(rval), (Byte *)rval); } - case 'u': - if (tstr[1] == endc || tstr[1] == ':') { - newStr = VarUniq(nstr); - cp = tstr + 1; - termc = *cp; - break; - } - goto default_case; -#ifdef SUNSHCMD - case 's': - if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) { - const char *emsg; - newStr = Cmd_Exec(nstr, &emsg); - if (emsg) - Error(emsg, nstr); - cp = tstr + 2; - termc = *cp; - break; - } - goto default_case; -#endif - default: - default_case: + if (freeIt) + free(freeIt); + tstr += rlen - 1; + } + else + Buf_AddByte(buf, (Byte)*tstr); + } + if (*tstr == ':') { + haveModifier = TRUE; + } else if (*tstr != '\0') { + haveModifier = FALSE; + } else { + /* + * If we never did find the end character, return NULL + * right now, setting the length to be the distance to + * the end of the string, since that's what make does. + */ + *lengthPtr = tstr - str; + return (var_Error); + } + *WR(tstr) = '\0'; + Buf_AddByte(buf, (Byte)'\0'); + str = Buf_GetAll(buf, NULL); + vlen = strlen(str); + + v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && + (vlen == 2) && (str[1] == 'F' || str[1] == 'D')) + { + /* + * Check for bogus D and F forms of local variables since we're + * in a local context and the name is the right length. + */ + switch(*str) { + case '@': + case '%': + case '*': + case '!': + case '>': + case '<': { -#ifdef SYSVVARSUB - /* - * This can either be a bogus modifier or a System-V - * substitution command. - */ - VarPattern pattern; - Boolean eqFound; + char vname[2]; + char *val; - pattern.flags = 0; - eqFound = FALSE; /* - * First we make a pass through the string trying - * to verify it is a SYSV-make-style translation: - * it must be: =) + * Well, it's local -- go look for it. */ - cp = tstr; - cnt = 1; - while (*cp != '\0' && cnt) { - if (*cp == '=') { - eqFound = TRUE; - /* continue looking for endc */ - } - else if (*cp == endc) - cnt--; - else if (*cp == startc) - cnt++; - if (cnt) - cp++; - } - if (*cp == endc && eqFound) { + vname[0] = *str; + vname[1] = '\0'; + v = VarFind(vname, ctxt, 0); + if (v != (Var *)NIL) { /* - * Now we break this sucker into the lhs and - * rhs. We must null terminate them of course. + * No need for nested expansion or anything, as we're + * the only one who sets these things and we sure don't + * but nested invocations in them... */ - delim='='; - cp = tstr; - if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, - err, &cp, delim, &pattern.flags, - &pattern.leftLen, NULL)) == NULL) - goto cleanup; - delim = endc; - if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, - err, &cp, delim, NULL, &pattern.rightLen, - &pattern)) == NULL) - goto cleanup; + val = (char *)Buf_GetAll(v->val, NULL); + if (str[1] == 'D') { + val = VarModify(ctxt, &parsestate, val, VarHead, + (ClientData)0); + } else { + val = VarModify(ctxt, &parsestate, val, VarTail, + (ClientData)0); + } /* - * SYSV modifications happen through the whole - * string. Note the pattern is anchored at the end. + * Resulting string is dynamically allocated, so + * tell caller to free it. */ - termc = *--cp; - delim = '\0'; - newStr = VarModify(ctxt, &parsestate, nstr, - VarSYSVMatch, - (ClientData)&pattern); - free(UNCONST(pattern.lhs)); - free(UNCONST(pattern.rhs)); - } else -#endif - { - Error("Unknown modifier '%c'", *tstr); - for (cp = tstr+1; - *cp != ':' && *cp != endc && *cp != '\0'; - cp++) - continue; - termc = *cp; - newStr = var_Error; + *freePtr = val; + *lengthPtr = tstr-start+1; + *WR(tstr) = endc; + Buf_Destroy(buf, TRUE); + VarFreeEnv(v, TRUE); + return(val); } + break; } } - if (DEBUG(VAR)) { - printf("Result is \"%s\"\n", newStr); - } + } - if (newStr != nstr) { - if (*freePtr) { - free(nstr); + if (v == (Var *)NIL) { + if (((vlen == 1) || + (((vlen == 2) && (str[1] == 'F' || + str[1] == 'D')))) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (*str) { + case '@': + case '%': + case '*': + case '!': + dynamic = TRUE; + break; } - nstr = newStr; - if (nstr != var_Error && nstr != varNoError) { - *freePtr = nstr; - } else { - *freePtr = NULL; + } else if ((vlen > 2) && (*str == '.') && + isupper((unsigned char) str[1]) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + int len; + + len = vlen - 1; + if ((strncmp(str, ".TARGET", len) == 0) || + (strncmp(str, ".ARCHIVE", len) == 0) || + (strncmp(str, ".PREFIX", len) == 0) || + (strncmp(str, ".MEMBER", len) == 0)) + { + dynamic = TRUE; } } - if (termc == '\0') { - Error("Unclosed variable specification for %s", v->name); - } else if (termc == ':') { - *WR(cp) = termc; - cp++; + + if (!haveModifier) { + /* + * No modifiers -- have specification length so we can return + * now. + */ + *lengthPtr = tstr - start + 1; + *WR(tstr) = endc; + if (dynamic) { + char *pstr = emalloc(*lengthPtr + 1); + strncpy(pstr, start, *lengthPtr); + pstr[*lengthPtr] = '\0'; + *freePtr = pstr; + Buf_Destroy(buf, TRUE); + return(pstr); + } else { + Buf_Destroy(buf, TRUE); + return (err ? var_Error : varNoError); + } } else { - *WR(cp) = termc; + /* + * Still need to get to the end of the variable specification, + * so kludge up a Var structure for the modifications + */ + v = emalloc(sizeof(Var)); + v->name = UNCONST(str); + v->val = Buf_Init(1); + v->flags = VAR_JUNK; + Buf_Destroy(buf, FALSE); } - tstr = cp; - } + } else + Buf_Destroy(buf, TRUE); + } + + + if (v->flags & VAR_IN_USE) { + Fatal("Variable %s is recursive.", v->name); + /*NOTREACHED*/ + } else { + v->flags |= VAR_IN_USE; + } + /* + * Before doing any modification, we have to make sure the value + * has been fully expanded. If it looks like recursion might be + * necessary (there's a dollar sign somewhere in the variable's value) + * we just call Var_Subst to do any other substitutions that are + * necessary. Note that the value returned by Var_Subst will have + * been dynamically-allocated, so it will need freeing when we + * return. + */ + nstr = (char *)Buf_GetAll(v->val, NULL); + if (strchr(nstr, '$') != NULL) { + nstr = Var_Subst(NULL, nstr, ctxt, err); + *freePtr = nstr; + } + + v->flags &= ~VAR_IN_USE; + + if ((nstr != NULL) && haveModifier) { + int used; + /* + * Skip initial colon while putting it back. + */ + *WR(tstr) = ':'; + tstr++; + + nstr = ApplyModifiers(nstr, tstr, startc, endc, + v, ctxt, err, &used, freePtr); + tstr += used; *lengthPtr = tstr - start + 1; } else { *lengthPtr = tstr - start + 1; @@ -3246,8 +3313,8 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, if (!(v->flags & VAR_KEEP)) { if (*freePtr) { free(nstr); + *freePtr = NULL; } - *freePtr = NULL; if (dynamic) { nstr = emalloc(*lengthPtr + 1); strncpy(nstr, start, *lengthPtr); @@ -3262,32 +3329,7 @@ Var_Parse(const char *str, GNode *ctxt, Boolean err, int *lengthPtr, free(v->name); free(v); } - /* - * XXX: If we need to free tstr2 - tstr is no longer valid either. - */ - if (tstr2) - free(tstr2); return (nstr); - - bad_modifier: - /* "{(" */ - Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr, - v->name); - -cleanup: - *lengthPtr = cp - start + 1; - if (*freePtr) { - free(nstr); - *freePtr = NULL; - } - if (tstr2) - free(tstr2); - if (delim != '\0') - Error("Unclosed substitution for %s (%c missing)", - v->name, delim); - if (v->flags & VAR_JUNK) - free(v); - return (var_Error); } /*- -- cgit