1 /* $NetBSD: for.c,v 1.73 2020/09/06 19:30:53 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.73 2020/09/06 19:30:53 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.73 2020/09/06 19:30:53 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 char ch; 337 338 const char *item = strlist_str(items, item_no); 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 (!(strlist_info(items, item_no) & 343 (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) { 344 Buf_AddStr(cmds, item); 345 return; 346 } 347 348 /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */ 349 while ((ch = *item++) != 0) { 350 if (ch == '$') { 351 size_t len = for_var_len(item); 352 if (len != 0) { 353 Buf_AddBytes(cmds, item - 1, len + 1); 354 item += len; 355 continue; 356 } 357 Buf_AddByte(cmds, '\\'); 358 } else if (ch == ':' || ch == '\\' || ch == ech) 359 Buf_AddByte(cmds, '\\'); 360 Buf_AddByte(cmds, ch); 361 } 362 } 363 364 static char * 365 ForIterate(void *v_arg, size_t *ret_len) 366 { 367 For *arg = v_arg; 368 int i; 369 char *var; 370 char *cp; 371 char *cmd_cp; 372 char *body_end; 373 char ch; 374 Buffer cmds; 375 size_t cmd_len; 376 377 if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) { 378 /* No more iterations */ 379 For_Free(arg); 380 return NULL; 381 } 382 383 free(arg->parse_buf); 384 arg->parse_buf = NULL; 385 386 /* 387 * Scan the for loop body and replace references to the loop variables 388 * with variable references that expand to the required text. 389 * Using variable expansions ensures that the .for loop can't generate 390 * syntax, and that the later parsing will still see a variable. 391 * We assume that the null variable will never be defined. 392 * 393 * The detection of substitions of the loop control variable is naive. 394 * Many of the modifiers use \ to escape $ (not $) so it is possible 395 * to contrive a makefile where an unwanted substitution happens. 396 */ 397 398 cmd_cp = Buf_GetAll(&arg->buf, &cmd_len); 399 body_end = cmd_cp + cmd_len; 400 Buf_Init(&cmds, cmd_len + 256); 401 for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) { 402 char ech; 403 ch = *++cp; 404 if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) { 405 cp++; 406 /* Check variable name against the .for loop variables */ 407 STRLIST_FOREACH(var, &arg->vars, i) { 408 size_t vlen = strlist_info(&arg->vars, i); 409 if (memcmp(cp, var, vlen) != 0) 410 continue; 411 if (cp[vlen] != ':' && cp[vlen] != ech && cp[vlen] != '\\') 412 continue; 413 /* Found a variable match. Replace with :U<value> */ 414 Buf_AddBytesBetween(&cmds, cmd_cp, cp); 415 Buf_AddStr(&cmds, ":U"); 416 cp += vlen; 417 cmd_cp = cp; 418 for_substitute(&cmds, &arg->items, arg->sub_next + i, ech); 419 break; 420 } 421 continue; 422 } 423 if (ch == 0) 424 break; 425 /* Probably a single character name, ignore $$ and stupid ones. {*/ 426 if (!arg->short_var || strchr("}):$", ch) != NULL) { 427 cp++; 428 continue; 429 } 430 STRLIST_FOREACH(var, &arg->vars, i) { 431 if (var[0] != ch || var[1] != 0) 432 continue; 433 /* Found a variable match. Replace with ${:U<value>} */ 434 Buf_AddBytesBetween(&cmds, cmd_cp, cp); 435 Buf_AddStr(&cmds, "{:U"); 436 cmd_cp = ++cp; 437 for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}'); 438 Buf_AddByte(&cmds, '}'); 439 break; 440 } 441 } 442 Buf_AddBytesBetween(&cmds, cmd_cp, body_end); 443 444 cp = Buf_Destroy(&cmds, FALSE); 445 if (DEBUG(FOR)) 446 (void)fprintf(debug_file, "For: loop body:\n%s", cp); 447 448 arg->sub_next += strlist_num(&arg->vars); 449 450 arg->parse_buf = cp; 451 *ret_len = strlen(cp); 452 return cp; 453 } 454 455 /* Run the for loop, imitating the actions of an include file. */ 456 void 457 For_Run(int lineno) 458 { 459 For *arg; 460 461 arg = accumFor; 462 accumFor = NULL; 463 464 if (strlist_num(&arg->items) == 0) { 465 /* Nothing to expand - possibly due to an earlier syntax error. */ 466 For_Free(arg); 467 return; 468 } 469 470 Parse_SetInput(NULL, lineno, -1, ForIterate, arg); 471 } 472