1 /* $NetBSD: for.c,v 1.63 2020/08/09 19:51:02 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.63 2020/08/09 19:51:02 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.63 2020/08/09 19:51:02 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 <assert.h> 56 #include <ctype.h> 57 58 #include "make.h" 59 #include "hash.h" 60 #include "dir.h" 61 #include "buf.h" 62 #include "strlist.h" 63 64 #define FOR_SUB_ESCAPE_CHAR 1 65 #define FOR_SUB_ESCAPE_BRACE 2 66 #define FOR_SUB_ESCAPE_PAREN 4 67 68 /* 69 * For statements are of the form: 70 * 71 * .for <variable> in <varlist> 72 * ... 73 * .endfor 74 * 75 * The trick is to look for the matching end inside for for loop 76 * To do that, we count the current nesting level of the for loops. 77 * and the .endfor statements, accumulating all the statements between 78 * the initial .for loop and the matching .endfor; 79 * then we evaluate the for loop for each variable in the varlist. 80 * 81 * Note that any nested fors are just passed through; they get handled 82 * recursively in For_Eval when we're expanding the enclosing for in 83 * For_Run. 84 */ 85 86 static int forLevel = 0; /* Nesting level */ 87 88 /* 89 * State of a for loop. 90 */ 91 typedef struct { 92 Buffer buf; /* Body of loop */ 93 strlist_t vars; /* Iteration variables */ 94 strlist_t items; /* Substitution items */ 95 char *parse_buf; 96 int short_var; 97 int sub_next; 98 } For; 99 100 static For *accumFor; /* Loop being accumulated */ 101 102 103 static char * 104 make_str(const char *ptr, int len) 105 { 106 char *new_ptr; 107 108 new_ptr = bmake_malloc(len + 1); 109 memcpy(new_ptr, ptr, len); 110 new_ptr[len] = 0; 111 return new_ptr; 112 } 113 114 static void 115 For_Free(For *arg) 116 { 117 Buf_Destroy(&arg->buf, TRUE); 118 strlist_clean(&arg->vars); 119 strlist_clean(&arg->items); 120 free(arg->parse_buf); 121 122 free(arg); 123 } 124 125 /*- 126 *----------------------------------------------------------------------- 127 * For_Eval -- 128 * Evaluate the for loop in the passed line. The line 129 * looks like this: 130 * .for <variable> in <varlist> 131 * 132 * Input: 133 * line Line to parse 134 * 135 * Results: 136 * 0: Not a .for statement, parse the line 137 * 1: We found a for loop 138 * -1: A .for statement with a bad syntax error, discard. 139 * 140 * Side Effects: 141 * None. 142 * 143 *----------------------------------------------------------------------- 144 */ 145 int 146 For_Eval(char *line) 147 { 148 For *new_for; 149 char *ptr = line, *sub; 150 int len; 151 int escapes; 152 unsigned char ch; 153 char **words, *word_buf; 154 int n, nwords; 155 156 /* Skip the '.' and any following whitespace */ 157 for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++) 158 continue; 159 160 /* 161 * If we are not in a for loop quickly determine if the statement is 162 * a for. 163 */ 164 if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' || 165 !isspace((unsigned char)ptr[3])) { 166 if (ptr[0] == 'e' && strncmp(ptr + 1, "ndfor", 5) == 0) { 167 Parse_Error(PARSE_FATAL, "for-less endfor"); 168 return -1; 169 } 170 return 0; 171 } 172 ptr += 3; 173 174 /* 175 * we found a for loop, and now we are going to parse it. 176 */ 177 178 new_for = bmake_malloc(sizeof *new_for); 179 memset(new_for, 0, sizeof *new_for); 180 181 /* Grab the variables. Terminate on "in". */ 182 for (;; ptr += len) { 183 while (*ptr && isspace((unsigned char)*ptr)) 184 ptr++; 185 if (*ptr == '\0') { 186 Parse_Error(PARSE_FATAL, "missing `in' in for"); 187 For_Free(new_for); 188 return -1; 189 } 190 for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++) 191 continue; 192 if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') { 193 ptr += 2; 194 break; 195 } 196 if (len == 1) 197 new_for->short_var = 1; 198 strlist_add_str(&new_for->vars, make_str(ptr, len), len); 199 } 200 201 if (strlist_num(&new_for->vars) == 0) { 202 Parse_Error(PARSE_FATAL, "no iteration variables in for"); 203 For_Free(new_for); 204 return -1; 205 } 206 207 while (*ptr && isspace((unsigned char)*ptr)) 208 ptr++; 209 210 /* 211 * Make a list with the remaining words 212 * The values are substituted as ${:U<value>...} so we must \ escape 213 * characters that break that syntax. 214 * Variables are fully expanded - so it is safe for escape $. 215 * We can't do the escapes here - because we don't know whether 216 * we are substuting into ${...} or $(...). 217 */ 218 sub = Var_Subst(ptr, VAR_GLOBAL, VARE_WANTRES); 219 220 /* 221 * Split into words allowing for quoted strings. 222 */ 223 words = brk_string(sub, &nwords, FALSE, &word_buf); 224 225 free(sub); 226 227 if (words != NULL) { 228 for (n = 0; n < nwords; n++) { 229 ptr = words[n]; 230 if (!*ptr) 231 continue; 232 escapes = 0; 233 while ((ch = *ptr++)) { 234 switch (ch) { 235 case ':': 236 case '$': 237 case '\\': 238 escapes |= FOR_SUB_ESCAPE_CHAR; 239 break; 240 case ')': 241 escapes |= FOR_SUB_ESCAPE_PAREN; 242 break; 243 case /*{*/ '}': 244 escapes |= FOR_SUB_ESCAPE_BRACE; 245 break; 246 } 247 } 248 /* 249 * We have to dup words[n] to maintain the semantics of 250 * strlist. 251 */ 252 strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes); 253 } 254 255 free(words); 256 free(word_buf); 257 258 if ((len = strlist_num(&new_for->items)) > 0 && 259 len % (n = strlist_num(&new_for->vars))) { 260 Parse_Error(PARSE_FATAL, 261 "Wrong number of words (%d) in .for substitution list" 262 " with %d vars", len, n); 263 /* 264 * Return 'success' so that the body of the .for loop is 265 * accumulated. 266 * Remove all items so that the loop doesn't iterate. 267 */ 268 strlist_clean(&new_for->items); 269 } 270 } 271 272 Buf_Init(&new_for->buf, 0); 273 accumFor = new_for; 274 forLevel = 1; 275 return 1; 276 } 277 278 /* 279 * Add another line to a .for loop. 280 * Returns 0 when the matching .endfor is reached. 281 */ 282 283 int 284 For_Accum(char *line) 285 { 286 char *ptr = line; 287 288 if (*ptr == '.') { 289 290 for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++) 291 continue; 292 293 if (strncmp(ptr, "endfor", 6) == 0 && 294 (isspace((unsigned char)ptr[6]) || !ptr[6])) { 295 if (DEBUG(FOR)) 296 (void)fprintf(debug_file, "For: end for %d\n", forLevel); 297 if (--forLevel <= 0) 298 return 0; 299 } else if (strncmp(ptr, "for", 3) == 0 && 300 isspace((unsigned char)ptr[3])) { 301 forLevel++; 302 if (DEBUG(FOR)) 303 (void)fprintf(debug_file, "For: new loop %d\n", forLevel); 304 } 305 } 306 307 Buf_AddStr(&accumFor->buf, line); 308 Buf_AddByte(&accumFor->buf, '\n'); 309 return 1; 310 } 311 312 313 static size_t 314 for_var_len(const char *var) 315 { 316 char ch, var_start, var_end; 317 int depth; 318 size_t len; 319 320 var_start = *var; 321 if (var_start == 0) 322 /* just escape the $ */ 323 return 0; 324 325 if (var_start == '(') 326 var_end = ')'; 327 else if (var_start == '{') 328 var_end = '}'; 329 else 330 /* Single char variable */ 331 return 1; 332 333 depth = 1; 334 for (len = 1; (ch = var[len++]) != 0;) { 335 if (ch == var_start) 336 depth++; 337 else if (ch == var_end && --depth == 0) 338 return len; 339 } 340 341 /* Variable end not found, escape the $ */ 342 return 0; 343 } 344 345 static void 346 for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech) 347 { 348 char ch; 349 350 const char *item = strlist_str(items, item_no); 351 352 /* If there were no escapes, or the only escape is the other variable 353 * terminator, then just substitute the full string */ 354 if (!(strlist_info(items, item_no) & 355 (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) { 356 Buf_AddStr(cmds, item); 357 return; 358 } 359 360 /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */ 361 while ((ch = *item++) != 0) { 362 if (ch == '$') { 363 size_t len = for_var_len(item); 364 if (len != 0) { 365 Buf_AddBytes(cmds, item - 1, len + 1); 366 item += len; 367 continue; 368 } 369 Buf_AddByte(cmds, '\\'); 370 } else if (ch == ':' || ch == '\\' || ch == ech) 371 Buf_AddByte(cmds, '\\'); 372 Buf_AddByte(cmds, ch); 373 } 374 } 375 376 static char * 377 For_Iterate(void *v_arg, size_t *ret_len) 378 { 379 For *arg = v_arg; 380 int i; 381 char *var; 382 char *cp; 383 char *cmd_cp; 384 char *body_end; 385 char ch; 386 Buffer cmds; 387 size_t cmd_len; 388 389 if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) { 390 /* No more iterations */ 391 For_Free(arg); 392 return NULL; 393 } 394 395 free(arg->parse_buf); 396 arg->parse_buf = NULL; 397 398 /* 399 * Scan the for loop body and replace references to the loop variables 400 * with variable references that expand to the required text. 401 * Using variable expansions ensures that the .for loop can't generate 402 * syntax, and that the later parsing will still see a variable. 403 * We assume that the null variable will never be defined. 404 * 405 * The detection of substitions of the loop control variable is naive. 406 * Many of the modifiers use \ to escape $ (not $) so it is possible 407 * to contrive a makefile where an unwanted substitution happens. 408 */ 409 410 cmd_cp = Buf_GetAll(&arg->buf, &cmd_len); 411 body_end = cmd_cp + cmd_len; 412 Buf_Init(&cmds, cmd_len + 256); 413 for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) { 414 char ech; 415 ch = *++cp; 416 if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) { 417 cp++; 418 /* Check variable name against the .for loop variables */ 419 STRLIST_FOREACH(var, &arg->vars, i) { 420 size_t vlen = strlist_info(&arg->vars, i); 421 if (memcmp(cp, var, vlen) != 0) 422 continue; 423 if (cp[vlen] != ':' && cp[vlen] != ech && cp[vlen] != '\\') 424 continue; 425 /* Found a variable match. Replace with :U<value> */ 426 Buf_AddBytesBetween(&cmds, cmd_cp, cp); 427 Buf_AddStr(&cmds, ":U"); 428 cp += vlen; 429 cmd_cp = cp; 430 for_substitute(&cmds, &arg->items, arg->sub_next + i, ech); 431 break; 432 } 433 continue; 434 } 435 if (ch == 0) 436 break; 437 /* Probably a single character name, ignore $$ and stupid ones. {*/ 438 if (!arg->short_var || strchr("}):$", ch) != NULL) { 439 cp++; 440 continue; 441 } 442 STRLIST_FOREACH(var, &arg->vars, i) { 443 if (var[0] != ch || var[1] != 0) 444 continue; 445 /* Found a variable match. Replace with ${:U<value>} */ 446 Buf_AddBytesBetween(&cmds, cmd_cp, cp); 447 Buf_AddStr(&cmds, "{:U"); 448 cmd_cp = ++cp; 449 for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}'); 450 Buf_AddByte(&cmds, '}'); 451 break; 452 } 453 } 454 Buf_AddBytesBetween(&cmds, cmd_cp, body_end); 455 456 cp = Buf_Destroy(&cmds, FALSE); 457 if (DEBUG(FOR)) 458 (void)fprintf(debug_file, "For: loop body:\n%s", cp); 459 460 arg->sub_next += strlist_num(&arg->vars); 461 462 arg->parse_buf = cp; 463 *ret_len = strlen(cp); 464 return cp; 465 } 466 467 /* Run the for loop, imitating the actions of an include file. */ 468 void 469 For_Run(int lineno) 470 { 471 For *arg; 472 473 arg = accumFor; 474 accumFor = NULL; 475 476 if (strlist_num(&arg->items) == 0) { 477 /* Nothing to expand - possibly due to an earlier syntax error. */ 478 For_Free(arg); 479 return; 480 } 481 482 Parse_SetInput(NULL, lineno, -1, For_Iterate, arg); 483 } 484