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