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