Home | History | Annotate | Line # | Download | only in make
for.c revision 1.74
      1 /*	$NetBSD: for.c,v 1.74 2020/09/07 05:58:08 rillig Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1992, The Regents of the University of California.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the University nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #ifndef MAKE_NATIVE
     33 static char rcsid[] = "$NetBSD: for.c,v 1.74 2020/09/07 05:58:08 rillig Exp $";
     34 #else
     35 #include <sys/cdefs.h>
     36 #ifndef lint
     37 #if 0
     38 static char sccsid[] = "@(#)for.c	8.1 (Berkeley) 6/6/93";
     39 #else
     40 __RCSID("$NetBSD: for.c,v 1.74 2020/09/07 05:58:08 rillig Exp $");
     41 #endif
     42 #endif /* not lint */
     43 #endif
     44 
     45 /*-
     46  * for.c --
     47  *	Functions to handle loops in a makefile.
     48  *
     49  * Interface:
     50  *	For_Eval 	Evaluate the loop in the passed line.
     51  *	For_Run		Run accumulated loop
     52  *
     53  */
     54 
     55 #include    "make.h"
     56 #include    "strlist.h"
     57 
     58 #define FOR_SUB_ESCAPE_CHAR  1
     59 #define FOR_SUB_ESCAPE_BRACE 2
     60 #define FOR_SUB_ESCAPE_PAREN 4
     61 
     62 /*
     63  * For statements are of the form:
     64  *
     65  * .for <variable> in <varlist>
     66  * ...
     67  * .endfor
     68  *
     69  * The trick is to look for the matching end inside for for loop
     70  * To do that, we count the current nesting level of the for loops.
     71  * and the .endfor statements, accumulating all the statements between
     72  * the initial .for loop and the matching .endfor;
     73  * then we evaluate the for loop for each variable in the varlist.
     74  *
     75  * Note that any nested fors are just passed through; they get handled
     76  * recursively in For_Eval when we're expanding the enclosing for in
     77  * For_Run.
     78  */
     79 
     80 static int forLevel = 0;	/* Nesting level */
     81 
     82 /*
     83  * State of a for loop.
     84  */
     85 typedef struct {
     86     Buffer buf;			/* Body of loop */
     87     strlist_t vars;		/* Iteration variables */
     88     strlist_t items;		/* Substitution items */
     89     char *parse_buf;
     90     /* Is any of the names 1 character long? If so, when the variable values
     91      * are substituted, the parser must handle $V expressions as well, not
     92      * only ${V} and $(V). */
     93     Boolean short_var;
     94     int sub_next;
     95 } For;
     96 
     97 static For *accumFor;		/* Loop being accumulated */
     98 
     99 
    100 static void
    101 For_Free(For *arg)
    102 {
    103     Buf_Destroy(&arg->buf, TRUE);
    104     strlist_clean(&arg->vars);
    105     strlist_clean(&arg->items);
    106     free(arg->parse_buf);
    107 
    108     free(arg);
    109 }
    110 
    111 /* Evaluate the for loop in the passed line. The line looks like this:
    112  *	.for <varname...> in <value...>
    113  *
    114  * Input:
    115  *	line		Line to parse
    116  *
    117  * Results:
    118  *      0: Not a .for statement, parse the line
    119  *	1: We found a for loop
    120  *     -1: A .for statement with a bad syntax error, discard.
    121  */
    122 int
    123 For_Eval(const char *line)
    124 {
    125     For *new_for;
    126     const char *ptr;
    127     Words words;
    128 
    129     /* Skip the '.' and any following whitespace */
    130     for (ptr = line + 1; isspace((unsigned char)*ptr); ptr++)
    131 	continue;
    132 
    133     /*
    134      * If we are not in a for loop quickly determine if the statement is
    135      * a for.
    136      */
    137     if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
    138 	!isspace((unsigned char)ptr[3])) {
    139 	if (ptr[0] == 'e' && strncmp(ptr + 1, "ndfor", 5) == 0) {
    140 	    Parse_Error(PARSE_FATAL, "for-less endfor");
    141 	    return -1;
    142 	}
    143 	return 0;
    144     }
    145     ptr += 3;
    146 
    147     /*
    148      * we found a for loop, and now we are going to parse it.
    149      */
    150 
    151     new_for = bmake_malloc(sizeof *new_for);
    152     Buf_Init(&new_for->buf, 0);
    153     strlist_init(&new_for->vars);
    154     strlist_init(&new_for->items);
    155     new_for->parse_buf = NULL;
    156     new_for->short_var = FALSE;
    157     new_for->sub_next = 0;
    158 
    159     /* Grab the variables. Terminate on "in". */
    160     while (TRUE) {
    161 	size_t len;
    162 
    163 	while (isspace((unsigned char)*ptr))
    164 	    ptr++;
    165 	if (*ptr == '\0') {
    166 	    Parse_Error(PARSE_FATAL, "missing `in' in for");
    167 	    For_Free(new_for);
    168 	    return -1;
    169 	}
    170 
    171 	for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
    172 	    continue;
    173 	if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
    174 	    ptr += 2;
    175 	    break;
    176 	}
    177 	if (len == 1)
    178 	    new_for->short_var = TRUE;
    179 
    180 	strlist_add_str(&new_for->vars, bmake_strldup(ptr, len), len);
    181 	ptr += len;
    182     }
    183 
    184     if (strlist_num(&new_for->vars) == 0) {
    185 	Parse_Error(PARSE_FATAL, "no iteration variables in for");
    186 	For_Free(new_for);
    187 	return -1;
    188     }
    189 
    190     while (isspace((unsigned char)*ptr))
    191 	ptr++;
    192 
    193     /*
    194      * Make a list with the remaining words.
    195      * The values are later substituted as ${:U<value>...} so we must
    196      * backslash-escape characters that break that syntax.
    197      * Variables are fully expanded - so it is safe for escape $.
    198      * We can't do the escapes here - because we don't know whether
    199      * we will be substituting into ${...} or $(...).
    200      */
    201     {
    202 	char *items = Var_Subst(ptr, VAR_GLOBAL, VARE_WANTRES);
    203 	words = Str_Words(items, FALSE);
    204 	free(items);
    205     }
    206 
    207     {
    208 	size_t n;
    209 
    210 	for (n = 0; n < words.len; n++) {
    211 	    int escapes;
    212 	    char ch;
    213 
    214 	    ptr = words.words[n];
    215 	    if (ptr[0] == '\0')
    216 		continue;
    217 	    escapes = 0;
    218 	    while ((ch = *ptr++)) {
    219 		switch (ch) {
    220 		case ':':
    221 		case '$':
    222 		case '\\':
    223 		    escapes |= FOR_SUB_ESCAPE_CHAR;
    224 		    break;
    225 		case ')':
    226 		    escapes |= FOR_SUB_ESCAPE_PAREN;
    227 		    break;
    228 		case '}':
    229 		    escapes |= FOR_SUB_ESCAPE_BRACE;
    230 		    break;
    231 		}
    232 	    }
    233 	    /*
    234 	     * We have to dup words[n] to maintain the semantics of
    235 	     * strlist.
    236 	     */
    237 	    strlist_add_str(&new_for->items, bmake_strdup(words.words[n]),
    238 			    escapes);
    239 	}
    240     }
    241 
    242     Words_Free(words);
    243 
    244     {
    245         size_t len, n;
    246 
    247 	if ((len = strlist_num(&new_for->items)) > 0 &&
    248 	    len % (n = strlist_num(&new_for->vars))) {
    249 	    Parse_Error(PARSE_FATAL,
    250 			"Wrong number of words (%zu) in .for substitution list"
    251 			" with %zu vars", len, n);
    252 	    /*
    253 	     * Return 'success' so that the body of the .for loop is
    254 	     * accumulated.
    255 	     * Remove all items so that the loop doesn't iterate.
    256 	     */
    257 	    strlist_clean(&new_for->items);
    258 	}
    259     }
    260 
    261     accumFor = new_for;
    262     forLevel = 1;
    263     return 1;
    264 }
    265 
    266 /*
    267  * Add another line to a .for loop.
    268  * Returns 0 when the matching .endfor is reached.
    269  */
    270 
    271 int
    272 For_Accum(const char *line)
    273 {
    274     const char *ptr = line;
    275 
    276     if (*ptr == '.') {
    277 
    278 	for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++)
    279 	    continue;
    280 
    281 	if (strncmp(ptr, "endfor", 6) == 0 &&
    282 	    (isspace((unsigned char)ptr[6]) || !ptr[6])) {
    283 	    if (DEBUG(FOR))
    284 		(void)fprintf(debug_file, "For: end for %d\n", forLevel);
    285 	    if (--forLevel <= 0)
    286 		return 0;
    287 	} else if (strncmp(ptr, "for", 3) == 0 &&
    288 		   isspace((unsigned char)ptr[3])) {
    289 	    forLevel++;
    290 	    if (DEBUG(FOR))
    291 		(void)fprintf(debug_file, "For: new loop %d\n", forLevel);
    292 	}
    293     }
    294 
    295     Buf_AddStr(&accumFor->buf, line);
    296     Buf_AddByte(&accumFor->buf, '\n');
    297     return 1;
    298 }
    299 
    300 
    301 static size_t
    302 for_var_len(const char *var)
    303 {
    304     char ch, var_start, var_end;
    305     int depth;
    306     size_t len;
    307 
    308     var_start = *var;
    309     if (var_start == 0)
    310 	/* just escape the $ */
    311 	return 0;
    312 
    313     if (var_start == '(')
    314 	var_end = ')';
    315     else if (var_start == '{')
    316 	var_end = '}';
    317     else
    318 	/* Single char variable */
    319 	return 1;
    320 
    321     depth = 1;
    322     for (len = 1; (ch = var[len++]) != 0;) {
    323 	if (ch == var_start)
    324 	    depth++;
    325 	else if (ch == var_end && --depth == 0)
    326 	    return len;
    327     }
    328 
    329     /* Variable end not found, escape the $ */
    330     return 0;
    331 }
    332 
    333 static void
    334 for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
    335 {
    336     const char *item = strlist_str(items, item_no);
    337     unsigned int escapes = strlist_info(items, item_no);
    338     char ch;
    339 
    340     /* If there were no escapes, or the only escape is the other variable
    341      * terminator, then just substitute the full string */
    342     if (!(escapes &
    343 	  (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
    344 	Buf_AddStr(cmds, item);
    345 	return;
    346     }
    347 
    348     /* Escape ':', '$', '\\' and 'ech' - these will be removed later by
    349      * :U processing, see ApplyModifier_Defined. */
    350     while ((ch = *item++) != 0) {
    351 	if (ch == '$') {
    352 	    size_t len = for_var_len(item);
    353 	    if (len != 0) {
    354 		Buf_AddBytes(cmds, item - 1, len + 1);
    355 		item += len;
    356 		continue;
    357 	    }
    358 	    Buf_AddByte(cmds, '\\');
    359 	} else if (ch == ':' || ch == '\\' || ch == ech)
    360 	    Buf_AddByte(cmds, '\\');
    361 	Buf_AddByte(cmds, ch);
    362     }
    363 }
    364 
    365 static char *
    366 ForIterate(void *v_arg, size_t *ret_len)
    367 {
    368     For *arg = v_arg;
    369     int i;
    370     char *var;
    371     char *cp;
    372     char *cmd_cp;
    373     char *body_end;
    374     char ch;
    375     Buffer cmds;
    376     size_t cmd_len;
    377 
    378     if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
    379 	/* No more iterations */
    380 	For_Free(arg);
    381 	return NULL;
    382     }
    383 
    384     free(arg->parse_buf);
    385     arg->parse_buf = NULL;
    386 
    387     /*
    388      * Scan the for loop body and replace references to the loop variables
    389      * with variable references that expand to the required text.
    390      * Using variable expansions ensures that the .for loop can't generate
    391      * syntax, and that the later parsing will still see a variable.
    392      * We assume that the null variable will never be defined.
    393      *
    394      * The detection of substitutions of the loop control variable is naive.
    395      * Many of the modifiers use \ to escape $ (not $) so it is possible
    396      * to contrive a makefile where an unwanted substitution happens.
    397      */
    398 
    399     cmd_cp = Buf_GetAll(&arg->buf, &cmd_len);
    400     body_end = cmd_cp + cmd_len;
    401     Buf_Init(&cmds, cmd_len + 256);
    402     for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
    403 	char ech;
    404 	ch = *++cp;
    405 	if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) {
    406 	    cp++;
    407 	    /* Check variable name against the .for loop variables */
    408 	    STRLIST_FOREACH(var, &arg->vars, i) {
    409 		size_t vlen = strlist_info(&arg->vars, i);
    410 		if (memcmp(cp, var, vlen) != 0)
    411 		    continue;
    412 		if (cp[vlen] != ':' && cp[vlen] != ech && cp[vlen] != '\\')
    413 		    continue;
    414 		/* Found a variable match. Replace with :U<value> */
    415 		Buf_AddBytesBetween(&cmds, cmd_cp, cp);
    416 		Buf_AddStr(&cmds, ":U");
    417 		cp += vlen;
    418 		cmd_cp = cp;
    419 		for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
    420 		break;
    421 	    }
    422 	    continue;
    423 	}
    424 	if (ch == 0)
    425 	    break;
    426 	/* Probably a single character name, ignore $$ and stupid ones. {*/
    427 	if (!arg->short_var || strchr("}):$", ch) != NULL) {
    428 	    cp++;
    429 	    continue;
    430 	}
    431 	STRLIST_FOREACH(var, &arg->vars, i) {
    432 	    if (var[0] != ch || var[1] != 0)
    433 		continue;
    434 	    /* Found a variable match. Replace with ${:U<value>} */
    435 	    Buf_AddBytesBetween(&cmds, cmd_cp, cp);
    436 	    Buf_AddStr(&cmds, "{:U");
    437 	    cmd_cp = ++cp;
    438 	    for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
    439 	    Buf_AddByte(&cmds, '}');
    440 	    break;
    441 	}
    442     }
    443     Buf_AddBytesBetween(&cmds, cmd_cp, body_end);
    444 
    445     cp = Buf_Destroy(&cmds, FALSE);
    446     if (DEBUG(FOR))
    447 	(void)fprintf(debug_file, "For: loop body:\n%s", cp);
    448 
    449     arg->sub_next += strlist_num(&arg->vars);
    450 
    451     arg->parse_buf = cp;
    452     *ret_len = strlen(cp);
    453     return cp;
    454 }
    455 
    456 /* Run the for loop, imitating the actions of an include file. */
    457 void
    458 For_Run(int lineno)
    459 {
    460     For *arg;
    461 
    462     arg = accumFor;
    463     accumFor = NULL;
    464 
    465     if (strlist_num(&arg->items) == 0) {
    466 	/* Nothing to expand - possibly due to an earlier syntax error. */
    467 	For_Free(arg);
    468 	return;
    469     }
    470 
    471     Parse_SetInput(NULL, lineno, -1, ForIterate, arg);
    472 }
    473