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