1 /* $NetBSD: cond.c,v 1.372 2025/04/10 21:41:35 rillig Exp $ */ 2 3 /* 4 * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Adam de Boor. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 /* 36 * Copyright (c) 1988, 1989 by Adam de Boor 37 * Copyright (c) 1989 by Berkeley Softworks 38 * All rights reserved. 39 * 40 * This code is derived from software contributed to Berkeley by 41 * Adam de Boor. 42 * 43 * Redistribution and use in source and binary forms, with or without 44 * modification, are permitted provided that the following conditions 45 * are met: 46 * 1. Redistributions of source code must retain the above copyright 47 * notice, this list of conditions and the following disclaimer. 48 * 2. Redistributions in binary form must reproduce the above copyright 49 * notice, this list of conditions and the following disclaimer in the 50 * documentation and/or other materials provided with the distribution. 51 * 3. All advertising materials mentioning features or use of this software 52 * must display the following acknowledgement: 53 * This product includes software developed by the University of 54 * California, Berkeley and its contributors. 55 * 4. Neither the name of the University nor the names of its contributors 56 * may be used to endorse or promote products derived from this software 57 * without specific prior written permission. 58 * 59 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 60 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 61 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 62 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 63 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 64 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 65 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 66 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 67 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 68 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 69 * SUCH DAMAGE. 70 */ 71 72 /* 73 * Handling of conditionals in a makefile. 74 * 75 * Interface: 76 * Cond_EvalLine Evaluate the conditional directive, such as 77 * '.if <cond>', '.elifnmake <cond>', '.else', '.endif'. 78 * 79 * Cond_EvalCondition 80 * Evaluate the conditional, which is either the argument 81 * of one of the .if directives or the condition in a 82 * ':?then:else' variable modifier. 83 * 84 * Cond_EndFile At the end of reading a makefile, ensure that the 85 * conditional directives are well-balanced. 86 */ 87 88 #include <errno.h> 89 90 #include "make.h" 91 #include "dir.h" 92 93 /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ 94 MAKE_RCSID("$NetBSD: cond.c,v 1.372 2025/04/10 21:41:35 rillig Exp $"); 95 96 /* 97 * Conditional expressions conform to this grammar: 98 * Or -> And ('||' And)* 99 * And -> Term ('&&' Term)* 100 * Term -> Function '(' Argument ')' 101 * Term -> Leaf Operator Leaf 102 * Term -> Leaf 103 * Term -> '(' Or ')' 104 * Term -> '!' Term 105 * Leaf -> "string" 106 * Leaf -> Number 107 * Leaf -> VariableExpression 108 * Leaf -> BareWord 109 * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' 110 * 111 * BareWord is an unquoted string literal, its evaluation depends on the kind 112 * of '.if' directive. 113 * 114 * The tokens are scanned by CondParser_Token, which returns: 115 * TOK_AND for '&&' 116 * TOK_OR for '||' 117 * TOK_NOT for '!' 118 * TOK_LPAREN for '(' 119 * TOK_RPAREN for ')' 120 * 121 * Other terminal symbols are evaluated using either the default function or 122 * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE 123 * or TOK_ERROR. 124 */ 125 typedef enum Token { 126 TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, 127 TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR 128 } Token; 129 130 typedef enum ComparisonOp { 131 LT, LE, GT, GE, EQ, NE 132 } ComparisonOp; 133 134 typedef struct CondParser { 135 136 /* 137 * The plain '.if ${VAR}' evaluates to true if the value of the 138 * expression has length > 0 and is not numerically zero. The other 139 * '.if' variants delegate to evalBare instead, for example '.ifdef 140 * ${VAR}' is equivalent to '.if defined(${VAR})', checking whether 141 * the variable named by the expression '${VAR}' is defined. 142 */ 143 bool plain; 144 145 /* The function to apply on unquoted bare words. */ 146 bool (*evalBare)(const char *); 147 bool negateEvalBare; 148 149 /* 150 * Whether the left-hand side of a comparison may be an unquoted 151 * string. This is allowed for expressions of the form 152 * ${condition:?:}, see ApplyModifier_IfElse. Such a condition is 153 * expanded before it is evaluated, due to ease of implementation. 154 * This means that at the point where the condition is evaluated, 155 * make cannot know anymore whether the left-hand side had originally 156 * been an expression or a plain word. 157 * 158 * In conditional directives like '.if', the left-hand side must 159 * either be an expression, a quoted string or a number. 160 */ 161 bool leftUnquotedOK; 162 163 const char *p; /* The remaining condition to parse */ 164 Token curr; /* Single push-back token used in parsing */ 165 } CondParser; 166 167 static CondResult CondParser_Or(CondParser *, bool); 168 169 unsigned int cond_depth = 0; /* current .if nesting level */ 170 171 /* Names for ComparisonOp. */ 172 static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; 173 174 MAKE_INLINE bool 175 skip_string(const char **pp, const char *str) 176 { 177 size_t len = strlen(str); 178 bool ok = strncmp(*pp, str, len) == 0; 179 if (ok) 180 *pp += len; 181 return ok; 182 } 183 184 static Token 185 ToToken(bool cond) 186 { 187 return cond ? TOK_TRUE : TOK_FALSE; 188 } 189 190 static void 191 CondParser_SkipWhitespace(CondParser *par) 192 { 193 cpp_skip_whitespace(&par->p); 194 } 195 196 /* 197 * Parse a single word, taking into account balanced parentheses as well as 198 * embedded expressions. Used for the argument of a built-in function as 199 * well as for bare words, which are then passed to the default function. 200 */ 201 static char * 202 ParseWord(const char **pp, bool doEval) 203 { 204 const char *p = *pp; 205 Buffer word; 206 int depth; 207 208 Buf_Init(&word); 209 210 depth = 0; 211 for (;;) { 212 char ch = *p; 213 if (ch == '\0' || ch == ' ' || ch == '\t') 214 break; 215 if ((ch == '&' || ch == '|') && depth == 0) 216 break; 217 if (ch == '$') { 218 VarEvalMode emode = doEval ? VARE_EVAL : VARE_PARSE; 219 FStr nestedVal = Var_Parse(&p, SCOPE_CMDLINE, emode); 220 /* TODO: handle errors */ 221 Buf_AddStr(&word, nestedVal.str); 222 FStr_Done(&nestedVal); 223 continue; 224 } 225 if (ch == '(') 226 depth++; 227 else if (ch == ')' && --depth < 0) 228 break; 229 Buf_AddByte(&word, ch); 230 p++; 231 } 232 233 *pp = p; 234 235 return Buf_DoneData(&word); 236 } 237 238 /* Parse the function argument, including the surrounding parentheses. */ 239 static char * 240 ParseFuncArg(const char **pp, bool doEval, const char *func) 241 { 242 const char *p = *pp, *argStart, *argEnd; 243 char *res; 244 245 p++; /* skip the '(' */ 246 cpp_skip_hspace(&p); 247 argStart = p; 248 res = ParseWord(&p, doEval); 249 argEnd = p; 250 cpp_skip_hspace(&p); 251 252 if (*p++ != ')') { 253 int len = 0; 254 while (ch_isalpha(func[len])) 255 len++; 256 257 Parse_Error(PARSE_FATAL, 258 "Missing ')' after argument '%.*s' for '%.*s'", 259 (int)(argEnd - argStart), argStart, len, func); 260 free(res); 261 return NULL; 262 } 263 264 *pp = p; 265 return res; 266 } 267 268 /* See if the given variable is defined. */ 269 static bool 270 FuncDefined(const char *var) 271 { 272 return Var_Exists(SCOPE_CMDLINE, var); 273 } 274 275 /* See if a target matching targetPattern is requested to be made. */ 276 static bool 277 FuncMake(const char *targetPattern) 278 { 279 StringListNode *ln; 280 bool warned = false; 281 282 for (ln = opts.create.first; ln != NULL; ln = ln->next) { 283 StrMatchResult res = Str_Match(ln->datum, targetPattern); 284 if (res.error != NULL && !warned) { 285 warned = true; 286 Parse_Error(PARSE_WARNING, 287 "%s in pattern argument '%s' to function 'make'", 288 res.error, targetPattern); 289 } 290 if (res.matched) 291 return true; 292 } 293 return false; 294 } 295 296 /* See if the given file exists. */ 297 static bool 298 FuncExists(const char *file) 299 { 300 bool result; 301 char *path; 302 303 path = Dir_FindFile(file, &dirSearchPath); 304 DEBUG2(COND, "exists(%s) result is \"%s\"\n", 305 file, path != NULL ? path : ""); 306 result = path != NULL; 307 free(path); 308 return result; 309 } 310 311 /* See if the given node exists and is an actual target. */ 312 static bool 313 FuncTarget(const char *node) 314 { 315 GNode *gn = Targ_FindNode(node); 316 return gn != NULL && GNode_IsTarget(gn); 317 } 318 319 /* 320 * See if the given node exists and is an actual target with commands 321 * associated with it. 322 */ 323 static bool 324 FuncCommands(const char *node) 325 { 326 GNode *gn = Targ_FindNode(node); 327 return gn != NULL && GNode_IsTarget(gn) && 328 !Lst_IsEmpty(&gn->commands); 329 } 330 331 /* 332 * Convert the string to a floating point number. Accepted formats are 333 * base-10 integer, base-16 integer and finite floating point numbers. 334 */ 335 static bool 336 TryParseNumber(const char *str, double *out_value) 337 { 338 char *end; 339 unsigned long ul_val; 340 double dbl_val; 341 342 if (str[0] == '\0') { /* XXX: why is an empty string a number? */ 343 *out_value = 0.0; 344 return true; 345 } 346 347 errno = 0; 348 ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); 349 if (*end == '\0' && errno != ERANGE) { 350 *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; 351 return true; 352 } 353 354 if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') 355 return false; /* skip the expensive strtod call */ 356 dbl_val = strtod(str, &end); 357 if (*end != '\0') 358 return false; 359 360 *out_value = dbl_val; 361 return true; 362 } 363 364 static bool 365 is_separator(char ch) 366 { 367 return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || 368 ch == '>' || ch == '<' || ch == ')' /* but not '(' */; 369 } 370 371 /* 372 * In a quoted or unquoted string literal or a number, parse an 373 * expression and add its value to the buffer. 374 * 375 * Return whether to continue parsing the leaf. 376 * 377 * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} 378 */ 379 static bool 380 CondParser_StringExpr(CondParser *par, const char *start, 381 bool doEval, bool quoted, 382 Buffer *buf, FStr *inout_str) 383 { 384 VarEvalMode emode; 385 const char *p; 386 bool atStart; /* true means an expression outside quotes */ 387 388 emode = doEval && quoted ? VARE_EVAL 389 : doEval ? VARE_EVAL_DEFINED_LOUD 390 : VARE_PARSE; 391 392 p = par->p; 393 atStart = p == start; 394 *inout_str = Var_Parse(&p, SCOPE_CMDLINE, emode); 395 /* TODO: handle errors */ 396 if (inout_str->str == var_Error) { 397 FStr_Done(inout_str); 398 *inout_str = FStr_InitRefer(NULL); 399 return false; 400 } 401 par->p = p; 402 403 if (atStart && is_separator(par->p[0])) 404 return false; 405 406 Buf_AddStr(buf, inout_str->str); 407 FStr_Done(inout_str); 408 *inout_str = FStr_InitRefer(NULL); /* not finished yet */ 409 return true; 410 } 411 412 /* 413 * Parse a string from an expression or an optionally quoted string, 414 * on the left-hand and right-hand sides of comparisons. 415 * 416 * Return the string without any enclosing quotes, or NULL on error. 417 * Sets out_quoted if the leaf was a quoted string literal. 418 */ 419 static FStr 420 CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, 421 bool *out_quoted) 422 { 423 Buffer buf; 424 FStr str; 425 bool quoted; 426 const char *start; 427 428 Buf_Init(&buf); 429 str = FStr_InitRefer(NULL); 430 *out_quoted = quoted = par->p[0] == '"'; 431 start = par->p; 432 if (quoted) 433 par->p++; 434 435 while (par->p[0] != '\0' && str.str == NULL) { 436 switch (par->p[0]) { 437 case '\\': 438 par->p++; 439 if (par->p[0] != '\0') { 440 Buf_AddByte(&buf, par->p[0]); 441 par->p++; 442 } 443 continue; 444 case '"': 445 par->p++; 446 if (quoted) 447 goto return_buf; /* skip the closing quote */ 448 Buf_AddByte(&buf, '"'); 449 continue; 450 case ')': /* see is_separator */ 451 case '!': 452 case '=': 453 case '>': 454 case '<': 455 case ' ': 456 case '\t': 457 if (!quoted) 458 goto return_buf; 459 Buf_AddByte(&buf, par->p[0]); 460 par->p++; 461 continue; 462 case '$': 463 if (!CondParser_StringExpr(par, 464 start, doEval, quoted, &buf, &str)) 465 goto return_str; 466 continue; 467 default: 468 if (!unquotedOK && !quoted && *start != '$' && 469 !ch_isdigit(*start)) { 470 str = FStr_InitRefer(NULL); 471 goto return_str; 472 } 473 Buf_AddByte(&buf, par->p[0]); 474 par->p++; 475 continue; 476 } 477 } 478 return_buf: 479 str = FStr_InitOwn(buf.data); 480 buf.data = NULL; 481 return_str: 482 Buf_Done(&buf); 483 return str; 484 } 485 486 /* 487 * Evaluate a "comparison without operator", such as in ".if ${VAR}" or 488 * ".if 0". 489 */ 490 static bool 491 EvalTruthy(CondParser *par, const char *value, bool quoted) 492 { 493 double num; 494 495 if (quoted) 496 return value[0] != '\0'; 497 if (TryParseNumber(value, &num)) 498 return num != 0.0; 499 if (par->plain) 500 return value[0] != '\0'; 501 return par->evalBare(value) != par->negateEvalBare; 502 } 503 504 /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 505 static bool 506 EvalCompareNum(double lhs, ComparisonOp op, double rhs) 507 { 508 DEBUG3(COND, "Comparing %f %s %f\n", lhs, opname[op], rhs); 509 510 switch (op) { 511 case LT: 512 return lhs < rhs; 513 case LE: 514 return lhs <= rhs; 515 case GT: 516 return lhs > rhs; 517 case GE: 518 return lhs >= rhs; 519 case EQ: 520 return lhs == rhs; 521 default: 522 return lhs != rhs; 523 } 524 } 525 526 static Token 527 EvalCompareStr(const char *lhs, ComparisonOp op, const char *rhs) 528 { 529 if (op != EQ && op != NE) { 530 Parse_Error(PARSE_FATAL, 531 "Comparison with '%s' requires both operands " 532 "'%s' and '%s' to be numeric", 533 opname[op], lhs, rhs); 534 return TOK_ERROR; 535 } 536 537 DEBUG3(COND, "Comparing \"%s\" %s \"%s\"\n", lhs, opname[op], rhs); 538 return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); 539 } 540 541 /* Evaluate a comparison, such as "${VAR} == 12345". */ 542 static Token 543 EvalCompare(const char *lhs, bool lhsQuoted, 544 ComparisonOp op, const char *rhs, bool rhsQuoted) 545 { 546 double left, right; 547 548 if (!rhsQuoted && !lhsQuoted) 549 if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 550 return ToToken(EvalCompareNum(left, op, right)); 551 552 return EvalCompareStr(lhs, op, rhs); 553 } 554 555 static bool 556 CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) 557 { 558 const char *p = par->p; 559 560 if (p[0] == '<' && p[1] == '=') 561 return par->p += 2, *out_op = LE, true; 562 if (p[0] == '<') 563 return par->p += 1, *out_op = LT, true; 564 if (p[0] == '>' && p[1] == '=') 565 return par->p += 2, *out_op = GE, true; 566 if (p[0] == '>') 567 return par->p += 1, *out_op = GT, true; 568 if (p[0] == '=' && p[1] == '=') 569 return par->p += 2, *out_op = EQ, true; 570 if (p[0] == '!' && p[1] == '=') 571 return par->p += 2, *out_op = NE, true; 572 return false; 573 } 574 575 /* 576 * Parse a comparison condition such as: 577 * 578 * 0 579 * ${VAR:Mpattern} 580 * ${VAR} == value 581 * ${VAR:U0} < 12345 582 */ 583 static Token 584 CondParser_Comparison(CondParser *par, bool doEval) 585 { 586 Token t = TOK_ERROR; 587 FStr lhs, rhs; 588 ComparisonOp op; 589 bool lhsQuoted, rhsQuoted; 590 591 lhs = CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhsQuoted); 592 if (lhs.str == NULL) 593 goto done_lhs; 594 595 CondParser_SkipWhitespace(par); 596 597 if (!CondParser_ComparisonOp(par, &op)) { 598 t = ToToken(doEval && EvalTruthy(par, lhs.str, lhsQuoted)); 599 goto done_lhs; 600 } 601 602 CondParser_SkipWhitespace(par); 603 604 if (par->p[0] == '\0') { 605 Parse_Error(PARSE_FATAL, 606 "Missing right-hand side of operator '%s'", opname[op]); 607 goto done_lhs; 608 } 609 610 rhs = CondParser_Leaf(par, doEval, true, &rhsQuoted); 611 t = rhs.str == NULL ? TOK_ERROR 612 : !doEval ? TOK_FALSE 613 : EvalCompare(lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); 614 FStr_Done(&rhs); 615 616 done_lhs: 617 FStr_Done(&lhs); 618 return t; 619 } 620 621 /* 622 * The argument to empty() is a variable name, optionally followed by 623 * variable modifiers. 624 */ 625 static bool 626 CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) 627 { 628 const char *p = par->p; 629 Token tok; 630 FStr val; 631 632 if (!skip_string(&p, "empty")) 633 return false; 634 635 cpp_skip_whitespace(&p); 636 if (*p != '(') 637 return false; 638 639 p--; /* Make p[1] point to the '('. */ 640 val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_EVAL : VARE_PARSE); 641 /* TODO: handle errors */ 642 643 if (val.str == var_Error) 644 tok = TOK_ERROR; 645 else { 646 cpp_skip_whitespace(&val.str); 647 tok = ToToken(doEval && val.str[0] == '\0'); 648 } 649 650 FStr_Done(&val); 651 *out_token = tok; 652 par->p = p; 653 return true; 654 } 655 656 /* Parse a function call expression, such as 'exists(${file})'. */ 657 static bool 658 CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) 659 { 660 char *arg; 661 const char *p = par->p; 662 bool (*fn)(const char *); 663 const char *fn_name = p; 664 665 if (skip_string(&p, "defined")) 666 fn = FuncDefined; 667 else if (skip_string(&p, "make")) 668 fn = FuncMake; 669 else if (skip_string(&p, "exists")) 670 fn = FuncExists; 671 else if (skip_string(&p, "target")) 672 fn = FuncTarget; 673 else if (skip_string(&p, "commands")) 674 fn = FuncCommands; 675 else 676 return false; 677 678 cpp_skip_whitespace(&p); 679 if (*p != '(') 680 return false; 681 682 arg = ParseFuncArg(&p, doEval, fn_name); 683 *out_token = ToToken(doEval && 684 arg != NULL && arg[0] != '\0' && fn(arg)); 685 free(arg); 686 687 par->p = p; 688 return true; 689 } 690 691 /* 692 * Parse a comparison that neither starts with '"' nor '$', such as the 693 * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without 694 * operator, which is a number, an expression or a string literal. 695 * 696 * TODO: Can this be merged into CondParser_Comparison? 697 */ 698 static Token 699 CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) 700 { 701 Token t; 702 char *arg; 703 const char *p; 704 705 p = par->p; 706 if (ch_isdigit(p[0]) || p[0] == '-' || p[0] == '+') 707 return CondParser_Comparison(par, doEval); 708 709 /* 710 * Most likely we have a bare word to apply the default function to. 711 * However, ".if a == b" gets here when the "a" is unquoted and 712 * doesn't start with a '$'. This surprises people. 713 * If what follows the function argument is a '=' or '!' then the 714 * syntax would be invalid if we did "defined(a)" - so instead treat 715 * as an expression. 716 */ 717 /* 718 * XXX: In edge cases, an expression may be evaluated twice, 719 * see cond-token-plain.mk, keyword 'twice'. 720 */ 721 arg = ParseWord(&p, doEval); 722 assert(arg[0] != '\0'); 723 cpp_skip_hspace(&p); 724 725 if (*p == '=' || *p == '!' || *p == '<' || *p == '>') { 726 free(arg); 727 return CondParser_Comparison(par, doEval); 728 } 729 par->p = p; 730 731 /* 732 * Evaluate the argument using the default function. 733 * This path always treats .if as .ifdef. To get here, the character 734 * after .if must have been taken literally, so the argument cannot 735 * be empty - even if it contained an expression. 736 */ 737 t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); 738 free(arg); 739 return t; 740 } 741 742 /* Return the next token or comparison result from the parser. */ 743 static Token 744 CondParser_Token(CondParser *par, bool doEval) 745 { 746 Token t; 747 748 t = par->curr; 749 if (t != TOK_NONE) { 750 par->curr = TOK_NONE; 751 return t; 752 } 753 754 cpp_skip_hspace(&par->p); 755 756 switch (par->p[0]) { 757 758 case '(': 759 par->p++; 760 return TOK_LPAREN; 761 762 case ')': 763 par->p++; 764 return TOK_RPAREN; 765 766 case '|': 767 par->p++; 768 if (par->p[0] == '|') 769 par->p++; 770 else { 771 Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 772 return TOK_ERROR; 773 } 774 return TOK_OR; 775 776 case '&': 777 par->p++; 778 if (par->p[0] == '&') 779 par->p++; 780 else { 781 Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 782 return TOK_ERROR; 783 } 784 return TOK_AND; 785 786 case '!': 787 par->p++; 788 return TOK_NOT; 789 790 case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 791 case '\n': /* XXX: why should this end the condition? */ 792 /* Probably obsolete now, from 1993-03-21. */ 793 case '\0': 794 return TOK_EOF; 795 796 case '"': 797 case '$': 798 return CondParser_Comparison(par, doEval); 799 800 default: 801 if (CondParser_FuncCallEmpty(par, doEval, &t)) 802 return t; 803 if (CondParser_FuncCall(par, doEval, &t)) 804 return t; 805 return CondParser_ComparisonOrLeaf(par, doEval); 806 } 807 } 808 809 /* Skip the next token if it equals t. */ 810 static bool 811 CondParser_Skip(CondParser *par, Token t) 812 { 813 Token actual; 814 815 actual = CondParser_Token(par, false); 816 if (actual == t) 817 return true; 818 819 assert(par->curr == TOK_NONE); 820 assert(actual != TOK_NONE); 821 par->curr = actual; 822 return false; 823 } 824 825 /* 826 * Term -> '(' Or ')' 827 * Term -> '!' Term 828 * Term -> Leaf Operator Leaf 829 * Term -> Leaf 830 */ 831 static CondResult 832 CondParser_Term(CondParser *par, bool doEval) 833 { 834 CondResult res; 835 Token t; 836 bool neg = false; 837 838 while ((t = CondParser_Token(par, doEval)) == TOK_NOT) 839 neg = !neg; 840 841 if (t == TOK_TRUE || t == TOK_FALSE) 842 return neg == (t == TOK_FALSE) ? CR_TRUE : CR_FALSE; 843 844 if (t == TOK_LPAREN) { 845 res = CondParser_Or(par, doEval); 846 if (res == CR_ERROR) 847 return CR_ERROR; 848 if (CondParser_Token(par, doEval) != TOK_RPAREN) 849 return CR_ERROR; 850 return neg == (res == CR_FALSE) ? CR_TRUE : CR_FALSE; 851 } 852 853 return CR_ERROR; 854 } 855 856 /* 857 * And -> Term ('&&' Term)* 858 */ 859 static CondResult 860 CondParser_And(CondParser *par, bool doEval) 861 { 862 CondResult res, rhs; 863 864 res = CR_TRUE; 865 do { 866 if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR) 867 return CR_ERROR; 868 if (rhs == CR_FALSE) { 869 res = CR_FALSE; 870 doEval = false; 871 } 872 } while (CondParser_Skip(par, TOK_AND)); 873 874 return res; 875 } 876 877 /* 878 * Or -> And ('||' And)* 879 */ 880 static CondResult 881 CondParser_Or(CondParser *par, bool doEval) 882 { 883 CondResult res, rhs; 884 885 res = CR_FALSE; 886 do { 887 if ((rhs = CondParser_And(par, doEval)) == CR_ERROR) 888 return CR_ERROR; 889 if (rhs == CR_TRUE) { 890 res = CR_TRUE; 891 doEval = false; 892 } 893 } while (CondParser_Skip(par, TOK_OR)); 894 895 return res; 896 } 897 898 /* 899 * Evaluate the condition, including any side effects from the 900 * expressions in the condition. The condition consists of &&, ||, !, 901 * function(arg), comparisons and parenthetical groupings thereof. 902 */ 903 static CondResult 904 CondEvalExpression(const char *cond, bool plain, 905 bool (*evalBare)(const char *), bool negate, 906 bool eprint, bool leftUnquotedOK) 907 { 908 CondParser par; 909 CondResult rval; 910 int parseErrorsBefore = parseErrors; 911 912 cpp_skip_hspace(&cond); 913 914 par.plain = plain; 915 par.evalBare = evalBare; 916 par.negateEvalBare = negate; 917 par.leftUnquotedOK = leftUnquotedOK; 918 par.p = cond; 919 par.curr = TOK_NONE; 920 921 DEBUG1(COND, "CondParser_Eval: %s\n", par.p); 922 rval = CondParser_Or(&par, true); 923 if (par.curr != TOK_EOF) 924 rval = CR_ERROR; 925 926 if (rval == CR_ERROR && eprint && parseErrors == parseErrorsBefore) 927 Parse_Error(PARSE_FATAL, "Malformed conditional '%s'", cond); 928 929 return rval; 930 } 931 932 /* 933 * Evaluate a condition in a :? modifier, such as 934 * ${"${VAR}" == value:?yes:no}. 935 */ 936 CondResult 937 Cond_EvalCondition(const char *cond) 938 { 939 return CondEvalExpression(cond, true, 940 FuncDefined, false, false, true); 941 } 942 943 static bool 944 IsEndif(const char *p) 945 { 946 return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && 947 p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); 948 } 949 950 static bool 951 DetermineKindOfConditional(const char **pp, bool *out_plain, 952 bool (**out_evalBare)(const char *), 953 bool *out_negate) 954 { 955 const char *p = *pp + 2; 956 957 *out_plain = false; 958 *out_evalBare = FuncDefined; 959 *out_negate = skip_string(&p, "n"); 960 961 if (skip_string(&p, "def")) { /* .ifdef and .ifndef */ 962 } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */ 963 *out_evalBare = FuncMake; 964 else if (!*out_negate) /* plain .if */ 965 *out_plain = true; 966 else 967 goto unknown_directive; 968 if (ch_isalpha(*p)) 969 goto unknown_directive; 970 971 *pp = p; 972 return true; 973 974 unknown_directive: 975 return false; 976 } 977 978 /* 979 * Evaluate the conditional directive in the line, which is one of: 980 * 981 * .if <cond> 982 * .ifmake <cond> 983 * .ifnmake <cond> 984 * .ifdef <cond> 985 * .ifndef <cond> 986 * .elif <cond> 987 * .elifmake <cond> 988 * .elifnmake <cond> 989 * .elifdef <cond> 990 * .elifndef <cond> 991 * .else 992 * .endif 993 * 994 * In these directives, <cond> consists of &&, ||, !, function(arg), 995 * comparisons, expressions, bare words, numbers and strings, and 996 * parenthetical groupings thereof. 997 * 998 * Results: 999 * CR_TRUE to continue parsing the lines that follow the 1000 * conditional (when <cond> evaluates to true) 1001 * CR_FALSE to skip the lines after the conditional 1002 * (when <cond> evaluates to false, or when a previous 1003 * branch was already taken) 1004 * CR_ERROR if the conditional was not valid, either because of 1005 * a syntax error or because some variable was undefined 1006 * or because the condition could not be evaluated 1007 */ 1008 CondResult 1009 Cond_EvalLine(const char *line) 1010 { 1011 typedef enum IfState { 1012 1013 /* None of the previous <cond> evaluated to true. */ 1014 IFS_INITIAL = 0, 1015 1016 /* 1017 * The previous <cond> evaluated to true. The lines following 1018 * this condition are interpreted. 1019 */ 1020 IFS_ACTIVE = 1 << 0, 1021 1022 /* The previous directive was an '.else'. */ 1023 IFS_SEEN_ELSE = 1 << 1, 1024 1025 /* One of the previous <cond> evaluated to true. */ 1026 IFS_WAS_ACTIVE = 1 << 2 1027 1028 } IfState; 1029 1030 static enum IfState *cond_states = NULL; 1031 static unsigned int cond_states_cap = 128; 1032 1033 bool plain; 1034 bool (*evalBare)(const char *); 1035 bool negate; 1036 bool isElif; 1037 CondResult res; 1038 IfState state; 1039 const char *p = line; 1040 1041 if (cond_states == NULL) { 1042 cond_states = bmake_malloc( 1043 cond_states_cap * sizeof *cond_states); 1044 cond_states[0] = IFS_ACTIVE; 1045 } 1046 1047 p++; /* skip the leading '.' */ 1048 cpp_skip_hspace(&p); 1049 1050 if (IsEndif(p)) { 1051 if (p[5] != '\0') { 1052 Parse_Error(PARSE_FATAL, 1053 "The .endif directive does not take arguments"); 1054 } 1055 1056 if (cond_depth == CurFile_CondMinDepth()) { 1057 Parse_Error(PARSE_FATAL, "if-less endif"); 1058 return CR_TRUE; 1059 } 1060 1061 /* Return state for previous conditional */ 1062 cond_depth--; 1063 Parse_GuardEndif(); 1064 return cond_states[cond_depth] & IFS_ACTIVE 1065 ? CR_TRUE : CR_FALSE; 1066 } 1067 1068 /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 1069 if (p[0] == 'e') { 1070 if (p[1] != 'l') 1071 return CR_ERROR; 1072 1073 /* Quite likely this is 'else' or 'elif' */ 1074 p += 2; 1075 if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) { 1076 if (p[2] != '\0') 1077 Parse_Error(PARSE_FATAL, 1078 "The .else directive " 1079 "does not take arguments"); 1080 1081 if (cond_depth == CurFile_CondMinDepth()) { 1082 Parse_Error(PARSE_FATAL, "if-less else"); 1083 return CR_TRUE; 1084 } 1085 Parse_GuardElse(); 1086 1087 state = cond_states[cond_depth]; 1088 if (state == IFS_INITIAL) { 1089 state = IFS_ACTIVE | IFS_SEEN_ELSE; 1090 } else { 1091 if (state & IFS_SEEN_ELSE) 1092 Parse_Error(PARSE_WARNING, 1093 "extra else"); 1094 state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 1095 } 1096 cond_states[cond_depth] = state; 1097 1098 return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE; 1099 } 1100 /* Assume for now it is an elif */ 1101 isElif = true; 1102 } else 1103 isElif = false; 1104 1105 if (p[0] != 'i' || p[1] != 'f') 1106 return CR_ERROR; 1107 1108 if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) 1109 return CR_ERROR; 1110 1111 if (isElif) { 1112 if (cond_depth == CurFile_CondMinDepth()) { 1113 Parse_Error(PARSE_FATAL, "if-less elif"); 1114 return CR_TRUE; 1115 } 1116 Parse_GuardElse(); 1117 state = cond_states[cond_depth]; 1118 if (state & IFS_SEEN_ELSE) { 1119 Parse_Error(PARSE_WARNING, "extra elif"); 1120 cond_states[cond_depth] = 1121 IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 1122 return CR_FALSE; 1123 } 1124 if (state != IFS_INITIAL) { 1125 cond_states[cond_depth] = IFS_WAS_ACTIVE; 1126 return CR_FALSE; 1127 } 1128 } else { 1129 /* Normal .if */ 1130 if (cond_depth + 1 >= cond_states_cap) { 1131 /* 1132 * This is rare, but not impossible. 1133 * In meta mode, dirdeps.mk (only runs at level 0) 1134 * can need more than the default. 1135 */ 1136 cond_states_cap += 32; 1137 cond_states = bmake_realloc(cond_states, 1138 cond_states_cap * sizeof *cond_states); 1139 } 1140 state = cond_states[cond_depth]; 1141 cond_depth++; 1142 if (!(state & IFS_ACTIVE)) { 1143 cond_states[cond_depth] = IFS_WAS_ACTIVE; 1144 return CR_FALSE; 1145 } 1146 } 1147 1148 res = CondEvalExpression(p, plain, evalBare, negate, true, false); 1149 if (res == CR_ERROR) { 1150 /* Syntax error, error message already output. */ 1151 /* Skip everything to the matching '.endif'. */ 1152 /* An extra '.else' is not detected in this case. */ 1153 cond_states[cond_depth] = IFS_WAS_ACTIVE; 1154 return CR_FALSE; 1155 } 1156 1157 cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL; 1158 return res; 1159 } 1160 1161 static bool 1162 ParseVarnameGuard(const char **pp, const char **varname) 1163 { 1164 const char *p = *pp; 1165 1166 if (ch_isalpha(*p) || *p == '_') { 1167 while (ch_isalnum(*p) || *p == '_') 1168 p++; 1169 *varname = *pp; 1170 *pp = p; 1171 return true; 1172 } 1173 return false; 1174 } 1175 1176 /* Extracts the multiple-inclusion guard from a conditional, if any. */ 1177 Guard * 1178 Cond_ExtractGuard(const char *line) 1179 { 1180 const char *p, *varname; 1181 Substring dir; 1182 Guard *guard; 1183 1184 p = line + 1; /* skip the '.' */ 1185 cpp_skip_hspace(&p); 1186 1187 dir.start = p; 1188 while (ch_isalpha(*p)) 1189 p++; 1190 dir.end = p; 1191 cpp_skip_hspace(&p); 1192 1193 if (Substring_Equals(dir, "if")) { 1194 if (skip_string(&p, "!defined(")) { 1195 if (ParseVarnameGuard(&p, &varname) 1196 && strcmp(p, ")") == 0) 1197 goto found_variable; 1198 } else if (skip_string(&p, "!target(")) { 1199 const char *arg_p = p; 1200 free(ParseWord(&p, false)); 1201 if (strcmp(p, ")") == 0) { 1202 guard = bmake_malloc(sizeof(*guard)); 1203 guard->kind = GK_TARGET; 1204 guard->name = ParseWord(&arg_p, true); 1205 return guard; 1206 } 1207 } 1208 } else if (Substring_Equals(dir, "ifndef")) { 1209 if (ParseVarnameGuard(&p, &varname) && *p == '\0') 1210 goto found_variable; 1211 } 1212 return NULL; 1213 1214 found_variable: 1215 guard = bmake_malloc(sizeof(*guard)); 1216 guard->kind = GK_VARIABLE; 1217 guard->name = bmake_strsedup(varname, p); 1218 return guard; 1219 } 1220 1221 void 1222 Cond_EndFile(void) 1223 { 1224 unsigned int open_conds = cond_depth - CurFile_CondMinDepth(); 1225 1226 if (open_conds != 0) { 1227 Parse_Error(PARSE_FATAL, "%u open conditional%s", 1228 open_conds, open_conds == 1 ? "" : "s"); 1229 cond_depth = CurFile_CondMinDepth(); 1230 } 1231 } 1232