Tue Sep 21 22:38:25 2021 UTC ()
make: do not allow unquoted 'left == right' after modifier ':?'

Having a static variable for state that clearly belongs in the parser
looked suspicious, and indeed it was wrong.

When the distinction between .if conditions and expressions of the form
${condition:?:} was added in cond.c 1.68 from 2015-05-05, a new unit
test was added, but it didn't cover this edge case.  At that time, the
state of the condition parser consisted of a few global variables
instead of a separate data type, as would have been appropriate for
parsing nested conditions.


(rillig)
diff -r1.275 -r1.276 src/usr.bin/make/cond.c
diff -r1.11 -r1.12 src/usr.bin/make/unit-tests/cond-token-plain.exp
diff -r1.11 -r1.12 src/usr.bin/make/unit-tests/cond-token-plain.mk

cvs diff -r1.275 -r1.276 src/usr.bin/make/cond.c (expand / switch to unified diff)

--- src/usr.bin/make/cond.c 2021/09/21 21:43:32 1.275
+++ src/usr.bin/make/cond.c 2021/09/21 22:38:25 1.276
@@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
1/* $NetBSD: cond.c,v 1.275 2021/09/21 21:43:32 rillig Exp $ */ 1/* $NetBSD: cond.c,v 1.276 2021/09/21 22:38:25 rillig Exp $ */
2 2
3/* 3/*
4 * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. 4 * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
5 * All rights reserved. 5 * All rights reserved.
6 * 6 *
7 * This code is derived from software contributed to Berkeley by 7 * This code is derived from software contributed to Berkeley by
8 * Adam de Boor. 8 * Adam de Boor.
9 * 9 *
10 * Redistribution and use in source and binary forms, with or without 10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions 11 * modification, are permitted provided that the following conditions
12 * are met: 12 * are met:
13 * 1. Redistributions of source code must retain the above copyright 13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer. 14 * notice, this list of conditions and the following disclaimer.
@@ -85,27 +85,27 @@ @@ -85,27 +85,27 @@
85 * Cond_restore_depth 85 * Cond_restore_depth
86 * Save and restore the nesting of the conditions, at 86 * Save and restore the nesting of the conditions, at
87 * the start and end of including another makefile, to 87 * the start and end of including another makefile, to
88 * ensure that in each makefile the conditional 88 * ensure that in each makefile the conditional
89 * directives are well-balanced. 89 * directives are well-balanced.
90 */ 90 */
91 91
92#include <errno.h> 92#include <errno.h>
93 93
94#include "make.h" 94#include "make.h"
95#include "dir.h" 95#include "dir.h"
96 96
97/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ 97/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
98MAKE_RCSID("$NetBSD: cond.c,v 1.275 2021/09/21 21:43:32 rillig Exp $"); 98MAKE_RCSID("$NetBSD: cond.c,v 1.276 2021/09/21 22:38:25 rillig Exp $");
99 99
100/* 100/*
101 * The parsing of conditional expressions is based on this grammar: 101 * The parsing of conditional expressions is based on this grammar:
102 * Or -> And 102 * Or -> And
103 * Or -> Or '||' And 103 * Or -> Or '||' And
104 * And -> Term 104 * And -> Term
105 * And -> And '&&' Term 105 * And -> And '&&' Term
106 * Term -> Function '(' Argument ')' 106 * Term -> Function '(' Argument ')'
107 * Term -> Leaf Operator Leaf 107 * Term -> Leaf Operator Leaf
108 * Term -> Leaf 108 * Term -> Leaf
109 * Term -> '(' Or ')' 109 * Term -> '(' Or ')'
110 * Term -> '!' Term 110 * Term -> '!' Term
111 * Leaf -> "string" 111 * Leaf -> "string"
@@ -144,58 +144,60 @@ typedef enum ComparisonOp { @@ -144,58 +144,60 @@ typedef enum ComparisonOp {
144typedef struct CondParser { 144typedef struct CondParser {
145 145
146 /* 146 /*
147 * The plain '.if ${VAR}' evaluates to true if the value of the 147 * The plain '.if ${VAR}' evaluates to true if the value of the
148 * expression has length > 0. The other '.if' variants delegate 148 * expression has length > 0. The other '.if' variants delegate
149 * to evalBare instead. 149 * to evalBare instead.
150 */ 150 */
151 bool plain; 151 bool plain;
152 152
153 /* The function to apply on unquoted bare words. */ 153 /* The function to apply on unquoted bare words. */
154 bool (*evalBare)(size_t, const char *); 154 bool (*evalBare)(size_t, const char *);
155 bool negateEvalBare; 155 bool negateEvalBare;
156 156
 157 /*
 158 * Whether the left-hand side of a comparison may NOT be an unquoted
 159 * string. This is allowed for expressions of the form
 160 * ${condition:?:}, see ApplyModifier_IfElse. Such a condition is
 161 * expanded before it is evaluated, due to ease of implementation.
 162 * This means that at the point where the condition is evaluated,
 163 * make cannot know anymore whether the left-hand side had originally
 164 * been a variable expression or a plain word.
 165 *
 166 * In all other contexts, the left-hand side must either be a
 167 * variable expression, a quoted string or a number.
 168 */
 169 bool lhsStrict;
 170
157 const char *p; /* The remaining condition to parse */ 171 const char *p; /* The remaining condition to parse */
158 Token curr; /* Single push-back token used in parsing */ 172 Token curr; /* Single push-back token used in parsing */
159 173
160 /* 174 /*
161 * Whether an error message has already been printed for this 175 * Whether an error message has already been printed for this
162 * condition. The first available error message is usually the most 176 * condition. The first available error message is usually the most
163 * specific one, therefore it makes sense to suppress the standard 177 * specific one, therefore it makes sense to suppress the standard
164 * "Malformed conditional" message. 178 * "Malformed conditional" message.
165 */ 179 */
166 bool printedError; 180 bool printedError;
167} CondParser; 181} CondParser;
168 182
169static CondResult CondParser_Or(CondParser *par, bool); 183static CondResult CondParser_Or(CondParser *par, bool);
170 184
171static unsigned int cond_depth = 0; /* current .if nesting level */ 185static unsigned int cond_depth = 0; /* current .if nesting level */
172static unsigned int cond_min_depth = 0; /* depth at makefile open */ 186static unsigned int cond_min_depth = 0; /* depth at makefile open */
173 187
174/* Names for ComparisonOp. */ 188/* Names for ComparisonOp. */
175static const char *opname[] = { "<", "<=", ">", ">=", "==", "!=" }; 189static const char *opname[] = { "<", "<=", ">", ">=", "==", "!=" };
176 190
177/* 
178 * Indicate when we should be strict about lhs of comparisons. 
179 * In strict mode, the lhs must be a variable expression or a string literal 
180 * in quotes. In non-strict mode it may also be an unquoted string literal. 
181 * 
182 * True when CondEvalExpression is called from Cond_EvalLine (.if etc). 
183 * False when CondEvalExpression is called from ApplyModifier_IfElse 
184 * since lhs is already expanded, and at that point we cannot tell if 
185 * it was a variable reference or not. 
186 */ 
187static bool lhsStrict; 
188 
189static bool 191static bool
190is_token(const char *str, const char *tok, size_t len) 192is_token(const char *str, const char *tok, size_t len)
191{ 193{
192 return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); 194 return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]);
193} 195}
194 196
195static Token 197static Token
196ToToken(bool cond) 198ToToken(bool cond)
197{ 199{
198 return cond ? TOK_TRUE : TOK_FALSE; 200 return cond ? TOK_TRUE : TOK_FALSE;
199} 201}
200 202
201/* Push back the most recent token read. We only need one level of this. */ 203/* Push back the most recent token read. We only need one level of this. */
@@ -672,27 +674,27 @@ length_1: @@ -672,27 +674,27 @@ length_1:
672 * 0 674 * 0
673 * ${VAR:Mpattern} 675 * ${VAR:Mpattern}
674 * ${VAR} == value 676 * ${VAR} == value
675 * ${VAR:U0} < 12345 677 * ${VAR:U0} < 12345
676 */ 678 */
677static Token 679static Token
678CondParser_Comparison(CondParser *par, bool doEval) 680CondParser_Comparison(CondParser *par, bool doEval)
679{ 681{
680 Token t = TOK_ERROR; 682 Token t = TOK_ERROR;
681 FStr lhs, rhs; 683 FStr lhs, rhs;
682 ComparisonOp op; 684 ComparisonOp op;
683 bool lhsQuoted, rhsQuoted; 685 bool lhsQuoted, rhsQuoted;
684 686
685 CondParser_Leaf(par, doEval, lhsStrict, &lhs, &lhsQuoted); 687 CondParser_Leaf(par, doEval, par->lhsStrict, &lhs, &lhsQuoted);
686 if (lhs.str == NULL) 688 if (lhs.str == NULL)
687 goto done_lhs; 689 goto done_lhs;
688 690
689 CondParser_SkipWhitespace(par); 691 CondParser_SkipWhitespace(par);
690 692
691 if (!CondParser_ComparisonOp(par, &op)) { 693 if (!CondParser_ComparisonOp(par, &op)) {
692 /* Unknown operator, compare against an empty string or 0. */ 694 /* Unknown operator, compare against an empty string or 0. */
693 t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); 695 t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted));
694 goto done_lhs; 696 goto done_lhs;
695 } 697 }
696 698
697 CondParser_SkipWhitespace(par); 699 CondParser_SkipWhitespace(par);
698 700
@@ -1053,33 +1055,32 @@ CondParser_Eval(CondParser *par, bool *o @@ -1053,33 +1055,32 @@ CondParser_Eval(CondParser *par, bool *o
1053 * COND_PARSE if the condition was valid grammatically 1055 * COND_PARSE if the condition was valid grammatically
1054 * COND_INVALID if not a valid conditional. 1056 * COND_INVALID if not a valid conditional.
1055 * 1057 *
1056 * (*value) is set to the boolean value of the condition 1058 * (*value) is set to the boolean value of the condition
1057 */ 1059 */
1058static CondEvalResult 1060static CondEvalResult
1059CondEvalExpression(const char *cond, bool *out_value, bool plain, 1061CondEvalExpression(const char *cond, bool *out_value, bool plain,
1060 bool (*evalBare)(size_t, const char *), bool negate, 1062 bool (*evalBare)(size_t, const char *), bool negate,
1061 bool eprint, bool strictLHS) 1063 bool eprint, bool strictLHS)
1062{ 1064{
1063 CondParser par; 1065 CondParser par;
1064 CondEvalResult rval; 1066 CondEvalResult rval;
1065 1067
1066 lhsStrict = strictLHS; 
1067 
1068 cpp_skip_hspace(&cond); 1068 cpp_skip_hspace(&cond);
1069 1069
1070 par.plain = plain; 1070 par.plain = plain;
1071 par.evalBare = evalBare; 1071 par.evalBare = evalBare;
1072 par.negateEvalBare = negate; 1072 par.negateEvalBare = negate;
 1073 par.lhsStrict = strictLHS;
1073 par.p = cond; 1074 par.p = cond;
1074 par.curr = TOK_NONE; 1075 par.curr = TOK_NONE;
1075 par.printedError = false; 1076 par.printedError = false;
1076 1077
1077 rval = CondParser_Eval(&par, out_value); 1078 rval = CondParser_Eval(&par, out_value);
1078 1079
1079 if (rval == COND_INVALID && eprint && !par.printedError) 1080 if (rval == COND_INVALID && eprint && !par.printedError)
1080 Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 1081 Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond);
1081 1082
1082 return rval; 1083 return rval;
1083} 1084}
1084 1085
1085/* 1086/*

cvs diff -r1.11 -r1.12 src/usr.bin/make/unit-tests/cond-token-plain.exp (expand / switch to unified diff)

--- src/usr.bin/make/unit-tests/cond-token-plain.exp 2021/09/21 21:59:56 1.11
+++ src/usr.bin/make/unit-tests/cond-token-plain.exp 2021/09/21 22:38:25 1.12
@@ -43,19 +43,19 @@ make: "cond-token-plain.mk" line 140: Mi @@ -43,19 +43,19 @@ make: "cond-token-plain.mk" line 140: Mi
43CondParser_Eval: == "" 43CondParser_Eval: == ""
44make: "cond-token-plain.mk" line 148: Malformed conditional (== "") 44make: "cond-token-plain.mk" line 148: Malformed conditional (== "")
45CondParser_Eval: \\ 45CondParser_Eval: \\
46make: "cond-token-plain.mk" line 163: The variable '\\' is not defined. 46make: "cond-token-plain.mk" line 163: The variable '\\' is not defined.
47CondParser_Eval: \\ 47CondParser_Eval: \\
48make: "cond-token-plain.mk" line 168: Now the variable '\\' is defined. 48make: "cond-token-plain.mk" line 168: Now the variable '\\' is defined.
49CondParser_Eval: "unquoted\"quoted" != unquoted"quoted 49CondParser_Eval: "unquoted\"quoted" != unquoted"quoted
50lhs = "unquoted"quoted", rhs = "unquoted"quoted", op = != 50lhs = "unquoted"quoted", rhs = "unquoted"quoted", op = !=
51CondParser_Eval: $$$$$$$$ != "" 51CondParser_Eval: $$$$$$$$ != ""
52CondParser_Eval: left == right 52CondParser_Eval: left == right
53make: "cond-token-plain.mk" line 191: Malformed conditional (left == right) 53make: "cond-token-plain.mk" line 191: Malformed conditional (left == right)
54CondParser_Eval: ${0:?:} || left == right 54CondParser_Eval: ${0:?:} || left == right
55CondParser_Eval: 0 55CondParser_Eval: 0
56lhs = "left", rhs = "right", op = == 56make: "cond-token-plain.mk" line 197: Malformed conditional (${0:?:} || left == right)
57CondParser_Eval: left == right || ${0:?:} 57CondParser_Eval: left == right || ${0:?:}
58make: "cond-token-plain.mk" line 201: Malformed conditional (left == right || ${0:?:}) 58make: "cond-token-plain.mk" line 202: Malformed conditional (left == right || ${0:?:})
59make: Fatal errors encountered -- cannot continue 59make: Fatal errors encountered -- cannot continue
60make: stopped in unit-tests 60make: stopped in unit-tests
61exit status 1 61exit status 1

cvs diff -r1.11 -r1.12 src/usr.bin/make/unit-tests/cond-token-plain.mk (expand / switch to unified diff)

--- src/usr.bin/make/unit-tests/cond-token-plain.mk 2021/09/21 21:59:56 1.11
+++ src/usr.bin/make/unit-tests/cond-token-plain.mk 2021/09/21 22:38:25 1.12
@@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
1# $NetBSD: cond-token-plain.mk,v 1.11 2021/09/21 21:59:56 rillig Exp $ 1# $NetBSD: cond-token-plain.mk,v 1.12 2021/09/21 22:38:25 rillig Exp $
2# 2#
3# Tests for plain tokens (that is, string literals without quotes) 3# Tests for plain tokens (that is, string literals without quotes)
4# in .if conditions. 4# in .if conditions.
5 5
6.MAKEFLAGS: -dc 6.MAKEFLAGS: -dc
7 7
8.if ${:Uvalue} != value 8.if ${:Uvalue} != value
9. error 9. error
10.endif 10.endif
11 11
12# Malformed condition since comment parsing is done in an early phase 12# Malformed condition since comment parsing is done in an early phase
13# and removes the '#' and everything behind it long before the condition 13# and removes the '#' and everything behind it long before the condition
14# parser gets to see it. 14# parser gets to see it.
@@ -183,26 +183,27 @@ ${:U\\\\}= backslash @@ -183,26 +183,27 @@ ${:U\\\\}= backslash
183. error 183. error
184.else 184.else
185. error 185. error
186.endif 186.endif
187 187
188# In a condition in an .if directive, the left-hand side must not be an 188# In a condition in an .if directive, the left-hand side must not be an
189# unquoted string literal. 189# unquoted string literal.
190# expect+1: Malformed conditional (left == right) 190# expect+1: Malformed conditional (left == right)
191.if left == right 191.if left == right
192.endif 192.endif
193# Before cond.c 1.276 from 2021-09-21, a variable expression containing the 193# Before cond.c 1.276 from 2021-09-21, a variable expression containing the
194# modifier ':?:' allowed unquoted string literals for the rest of the 194# modifier ':?:' allowed unquoted string literals for the rest of the
195# condition. This was an unintended implementation mistake. 195# condition. This was an unintended implementation mistake.
 196# expect+1: Malformed conditional (${0:?:} || left == right)
196.if ${0:?:} || left == right 197.if ${0:?:} || left == right
197.endif 198.endif
198# This affected only the comparisons after the expression, so the following 199# This affected only the comparisons after the expression, so the following
199# was still a syntax error. 200# was still a syntax error.
200# expect+1: Malformed conditional (left == right || ${0:?:}) 201# expect+1: Malformed conditional (left == right || ${0:?:})
201.if left == right || ${0:?:} 202.if left == right || ${0:?:}
202.endif 203.endif
203 204
204# See cond-token-string.mk for similar tests where the condition is enclosed 205# See cond-token-string.mk for similar tests where the condition is enclosed
205# in "quotes". 206# in "quotes".
206 207
207all: 208all:
208 @:; 209 @:;