1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmConditionEvaluator.h"
4
5 #include <array>
6 #include <cstdio>
7 #include <cstdlib>
8 #include <functional>
9 #include <iterator>
10 #include <list>
11 #include <sstream>
12 #include <utility>
13
14 #include <cm/string_view>
15 #include <cmext/algorithm>
16
17 #include "cmsys/RegularExpression.hxx"
18
19 #include "cmExpandedCommandArgument.h"
20 #include "cmMakefile.h"
21 #include "cmMessageType.h"
22 #include "cmState.h"
23 #include "cmStringAlgorithms.h"
24 #include "cmSystemTools.h"
25 #include "cmValue.h"
26 #include "cmake.h"
27
28 namespace {
29 auto const keyAND = "AND"_s;
30 auto const keyCOMMAND = "COMMAND"_s;
31 auto const keyDEFINED = "DEFINED"_s;
32 auto const keyEQUAL = "EQUAL"_s;
33 auto const keyEXISTS = "EXISTS"_s;
34 auto const keyGREATER = "GREATER"_s;
35 auto const keyGREATER_EQUAL = "GREATER_EQUAL"_s;
36 auto const keyIN_LIST = "IN_LIST"_s;
37 auto const keyIS_ABSOLUTE = "IS_ABSOLUTE"_s;
38 auto const keyIS_DIRECTORY = "IS_DIRECTORY"_s;
39 auto const keyIS_NEWER_THAN = "IS_NEWER_THAN"_s;
40 auto const keyIS_SYMLINK = "IS_SYMLINK"_s;
41 auto const keyLESS = "LESS"_s;
42 auto const keyLESS_EQUAL = "LESS_EQUAL"_s;
43 auto const keyMATCHES = "MATCHES"_s;
44 auto const keyNOT = "NOT"_s;
45 auto const keyOR = "OR"_s;
46 auto const keyParenL = "("_s;
47 auto const keyParenR = ")"_s;
48 auto const keyPOLICY = "POLICY"_s;
49 auto const keySTREQUAL = "STREQUAL"_s;
50 auto const keySTRGREATER = "STRGREATER"_s;
51 auto const keySTRGREATER_EQUAL = "STRGREATER_EQUAL"_s;
52 auto const keySTRLESS = "STRLESS"_s;
53 auto const keySTRLESS_EQUAL = "STRLESS_EQUAL"_s;
54 auto const keyTARGET = "TARGET"_s;
55 auto const keyTEST = "TEST"_s;
56 auto const keyVERSION_EQUAL = "VERSION_EQUAL"_s;
57 auto const keyVERSION_GREATER = "VERSION_GREATER"_s;
58 auto const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL"_s;
59 auto const keyVERSION_LESS = "VERSION_LESS"_s;
60 auto const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL"_s;
61
62 cmSystemTools::CompareOp const MATCH2CMPOP[5] = {
63 cmSystemTools::OP_LESS, cmSystemTools::OP_LESS_EQUAL,
64 cmSystemTools::OP_GREATER, cmSystemTools::OP_GREATER_EQUAL,
65 cmSystemTools::OP_EQUAL
66 };
67
68 // Run-Time to Compile-Time template selector
69 template <template <typename> class Comp, template <typename> class... Ops>
70 struct cmRt2CtSelector
71 {
72 template <typename T>
eval__anonac2ed0770111::cmRt2CtSelector73 static bool eval(int r, T lhs, T rhs)
74 {
75 switch (r) {
76 case 0:
77 return false;
78 case 1:
79 return Comp<T>()(lhs, rhs);
80 default:
81 return cmRt2CtSelector<Ops...>::eval(r - 1, lhs, rhs);
82 }
83 }
84 };
85
86 template <template <typename> class Comp>
87 struct cmRt2CtSelector<Comp>
88 {
89 template <typename T>
eval__anonac2ed0770111::cmRt2CtSelector90 static bool eval(int r, T lhs, T rhs)
91 {
92 return r == 1 && Comp<T>()(lhs, rhs);
93 }
94 };
95
bool2string(bool const value)96 std::string bool2string(bool const value)
97 {
98 return std::string(std::size_t(1), static_cast<char>('0' + int(value)));
99 }
100
looksLikeSpecialVariable(const std::string & var,cm::static_string_view prefix,const std::size_t varNameLen)101 bool looksLikeSpecialVariable(const std::string& var,
102 cm::static_string_view prefix,
103 const std::size_t varNameLen)
104 {
105 // NOTE Expecting a variable name at least 1 char length:
106 // <prefix> + `{` + <varname> + `}`
107 return ((prefix.size() + 3) <= varNameLen) &&
108 cmHasPrefix(var, cmStrCat(prefix, '{')) && var[varNameLen - 1] == '}';
109 }
110 } // anonymous namespace
111
112 #if defined(__SUNPRO_CC)
113 # define CM_INHERIT_CTOR(Class, Base, Tpl) \
114 template <typename... Args> \
115 Class(Args&&... args) \
116 : Base Tpl(std::forward<Args>(args)...) \
117 { \
118 }
119 #else
120 # define CM_INHERIT_CTOR(Class, Base, Tpl) using Base Tpl ::Base
121 #endif
122
123 // BEGIN cmConditionEvaluator::cmArgumentList
124 class cmConditionEvaluator::cmArgumentList
125 : public std::list<cmExpandedCommandArgument>
126 {
127 using base_t = std::list<cmExpandedCommandArgument>;
128
129 public:
130 CM_INHERIT_CTOR(cmArgumentList, list, <cmExpandedCommandArgument>);
131
132 class CurrentAndNextIter
133 {
134 friend class cmConditionEvaluator::cmArgumentList;
135
136 public:
137 base_t::iterator current;
138 base_t::iterator next;
139
advance(base_t & args)140 CurrentAndNextIter advance(base_t& args)
141 {
142 this->current = std::next(this->current);
143 this->next =
144 std::next(this->current, difference_type(this->current != args.end()));
145 return *this;
146 }
147
148 private:
CurrentAndNextIter(base_t & args)149 CurrentAndNextIter(base_t& args)
150 : current(args.begin())
151 , next(std::next(this->current,
152 difference_type(this->current != args.end())))
153 {
154 }
155 };
156
157 class CurrentAndTwoMoreIter
158 {
159 friend class cmConditionEvaluator::cmArgumentList;
160
161 public:
162 base_t::iterator current;
163 base_t::iterator next;
164 base_t::iterator nextnext;
165
advance(base_t & args)166 CurrentAndTwoMoreIter advance(base_t& args)
167 {
168 this->current = std::next(this->current);
169 this->next =
170 std::next(this->current, difference_type(this->current != args.end()));
171 this->nextnext =
172 std::next(this->next, difference_type(this->next != args.end()));
173 return *this;
174 }
175
176 private:
CurrentAndTwoMoreIter(base_t & args)177 CurrentAndTwoMoreIter(base_t& args)
178 : current(args.begin())
179 , next(std::next(this->current,
180 difference_type(this->current != args.end())))
181 , nextnext(
182 std::next(this->next, difference_type(this->next != args.end())))
183 {
184 }
185 };
186
make2ArgsIterator()187 CurrentAndNextIter make2ArgsIterator() { return *this; }
make3ArgsIterator()188 CurrentAndTwoMoreIter make3ArgsIterator() { return *this; }
189
190 template <typename Iter>
ReduceOneArg(const bool value,Iter args)191 void ReduceOneArg(const bool value, Iter args)
192 {
193 *args.current = cmExpandedCommandArgument(bool2string(value), true);
194 this->erase(args.next);
195 }
196
ReduceTwoArgs(const bool value,CurrentAndTwoMoreIter args)197 void ReduceTwoArgs(const bool value, CurrentAndTwoMoreIter args)
198 {
199 *args.current = cmExpandedCommandArgument(bool2string(value), true);
200 this->erase(args.nextnext);
201 this->erase(args.next);
202 }
203 };
204
205 // END cmConditionEvaluator::cmArgumentList
206
cmConditionEvaluator(cmMakefile & makefile,cmListFileBacktrace bt)207 cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile,
208 cmListFileBacktrace bt)
209 : Makefile(makefile)
210 , Backtrace(std::move(bt))
211 , Policy12Status(makefile.GetPolicyStatus(cmPolicies::CMP0012))
212 , Policy54Status(makefile.GetPolicyStatus(cmPolicies::CMP0054))
213 , Policy57Status(makefile.GetPolicyStatus(cmPolicies::CMP0057))
214 , Policy64Status(makefile.GetPolicyStatus(cmPolicies::CMP0064))
215 {
216 }
217
218 //=========================================================================
219 // order of operations,
220 // 1. ( ) -- parenthetical groups
221 // 2. IS_DIRECTORY EXISTS COMMAND DEFINED etc predicates
222 // 3. MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL etc binary ops
223 // 4. NOT
224 // 5. AND OR
225 //
226 // There is an issue on whether the arguments should be values of references,
227 // for example IF (FOO AND BAR) should that compare the strings FOO and BAR
228 // or should it really do IF (${FOO} AND ${BAR}) Currently IS_DIRECTORY
229 // EXISTS COMMAND and DEFINED all take values. EQUAL, LESS and GREATER can
230 // take numeric values or variable names. STRLESS and STRGREATER take
231 // variable names but if the variable name is not found it will use the name
232 // directly. AND OR take variables or the values 0 or 1.
233
IsTrue(const std::vector<cmExpandedCommandArgument> & args,std::string & errorString,MessageType & status)234 bool cmConditionEvaluator::IsTrue(
235 const std::vector<cmExpandedCommandArgument>& args, std::string& errorString,
236 MessageType& status)
237 {
238 errorString.clear();
239
240 // handle empty invocation
241 if (args.empty()) {
242 return false;
243 }
244
245 // store the reduced args in this vector
246 cmArgumentList newArgs(args.begin(), args.end());
247
248 // now loop through the arguments and see if we can reduce any of them
249 // we do this multiple times. Once for each level of precedence
250 // parens
251 using handlerFn_t = bool (cmConditionEvaluator::*)(
252 cmArgumentList&, std::string&, MessageType&);
253 const std::array<handlerFn_t, 5> handlers = { {
254 &cmConditionEvaluator::HandleLevel0, // parenthesis
255 &cmConditionEvaluator::HandleLevel1, // predicates
256 &cmConditionEvaluator::HandleLevel2, // binary ops
257 &cmConditionEvaluator::HandleLevel3, // NOT
258 &cmConditionEvaluator::HandleLevel4 // AND OR
259 } };
260 for (auto fn : handlers) {
261 // Call the reducer 'till there is anything to reduce...
262 // (i.e., if after an iteration the size becomes smaller)
263 auto levelResult = true;
264 for (auto beginSize = newArgs.size();
265 (levelResult = (this->*fn)(newArgs, errorString, status)) &&
266 newArgs.size() < beginSize;
267 beginSize = newArgs.size()) {
268 }
269
270 if (!levelResult) {
271 // NOTE `errorString` supposed to be set already
272 return false;
273 }
274 }
275
276 // now at the end there should only be one argument left
277 if (newArgs.size() != 1) {
278 errorString = "Unknown arguments specified";
279 status = MessageType::FATAL_ERROR;
280 return false;
281 }
282
283 return this->GetBooleanValueWithAutoDereference(newArgs.front(), errorString,
284 status, true);
285 }
286
287 //=========================================================================
GetDefinitionIfUnquoted(cmExpandedCommandArgument const & argument) const288 cmValue cmConditionEvaluator::GetDefinitionIfUnquoted(
289 cmExpandedCommandArgument const& argument) const
290 {
291 if ((this->Policy54Status != cmPolicies::WARN &&
292 this->Policy54Status != cmPolicies::OLD) &&
293 argument.WasQuoted()) {
294 return nullptr;
295 }
296
297 cmValue def = this->Makefile.GetDefinition(argument.GetValue());
298
299 if (def && argument.WasQuoted() &&
300 this->Policy54Status == cmPolicies::WARN) {
301 if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) {
302 std::ostringstream e;
303 // clang-format off
304 e << (cmPolicies::GetPolicyWarning(cmPolicies::CMP0054))
305 << "\n"
306 "Quoted variables like \"" << argument.GetValue() << "\" "
307 "will no longer be dereferenced when the policy is set to NEW. "
308 "Since the policy is not set the OLD behavior will be used.";
309 // clang-format on
310
311 this->Makefile.GetCMakeInstance()->IssueMessage(
312 MessageType::AUTHOR_WARNING, e.str(), this->Backtrace);
313 }
314 }
315
316 return def;
317 }
318
319 //=========================================================================
GetVariableOrString(const cmExpandedCommandArgument & argument) const320 cmValue cmConditionEvaluator::GetVariableOrString(
321 const cmExpandedCommandArgument& argument) const
322 {
323 cmValue def = this->GetDefinitionIfUnquoted(argument);
324
325 if (!def) {
326 def = cmValue(argument.GetValue());
327 }
328
329 return def;
330 }
331
332 //=========================================================================
IsKeyword(cm::static_string_view keyword,const cmExpandedCommandArgument & argument) const333 bool cmConditionEvaluator::IsKeyword(
334 cm::static_string_view keyword,
335 const cmExpandedCommandArgument& argument) const
336 {
337 if ((this->Policy54Status != cmPolicies::WARN &&
338 this->Policy54Status != cmPolicies::OLD) &&
339 argument.WasQuoted()) {
340 return false;
341 }
342
343 const auto isKeyword = argument.GetValue() == keyword;
344
345 if (isKeyword && argument.WasQuoted() &&
346 this->Policy54Status == cmPolicies::WARN) {
347 if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) {
348 std::ostringstream e;
349 // clang-format off
350 e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0054)
351 << "\n"
352 "Quoted keywords like \"" << argument.GetValue() << "\" "
353 "will no longer be interpreted as keywords "
354 "when the policy is set to NEW. "
355 "Since the policy is not set the OLD behavior will be used.";
356 // clang-format on
357
358 this->Makefile.GetCMakeInstance()->IssueMessage(
359 MessageType::AUTHOR_WARNING, e.str(), this->Backtrace);
360 }
361 }
362
363 return isKeyword;
364 }
365
366 //=========================================================================
GetBooleanValue(cmExpandedCommandArgument & arg) const367 bool cmConditionEvaluator::GetBooleanValue(
368 cmExpandedCommandArgument& arg) const
369 {
370 // Check basic and named constants.
371 if (cmIsOn(arg.GetValue())) {
372 return true;
373 }
374 if (cmIsOff(arg.GetValue())) {
375 return false;
376 }
377
378 // Check for numbers.
379 if (!arg.empty()) {
380 char* end;
381 const double d = std::strtod(arg.GetValue().c_str(), &end);
382 if (*end == '\0') {
383 // The whole string is a number. Use C conversion to bool.
384 return static_cast<bool>(d);
385 }
386 }
387
388 // Check definition.
389 cmValue def = this->GetDefinitionIfUnquoted(arg);
390 return !cmIsOff(def);
391 }
392
393 //=========================================================================
394 // Boolean value behavior from CMake 2.6.4 and below.
GetBooleanValueOld(cmExpandedCommandArgument const & arg,bool const one) const395 bool cmConditionEvaluator::GetBooleanValueOld(
396 cmExpandedCommandArgument const& arg, bool const one) const
397 {
398 if (one) {
399 // Old IsTrue behavior for single argument.
400 if (arg == "0") {
401 return false;
402 }
403 if (arg == "1") {
404 return true;
405 }
406 cmValue def = this->GetDefinitionIfUnquoted(arg);
407 return !cmIsOff(def);
408 }
409 // Old GetVariableOrNumber behavior.
410 cmValue def = this->GetDefinitionIfUnquoted(arg);
411 if (!def && std::atoi(arg.GetValue().c_str())) {
412 def = cmValue(arg.GetValue());
413 }
414 return !cmIsOff(def);
415 }
416
417 //=========================================================================
418 // returns the resulting boolean value
GetBooleanValueWithAutoDereference(cmExpandedCommandArgument & newArg,std::string & errorString,MessageType & status,bool const oneArg) const419 bool cmConditionEvaluator::GetBooleanValueWithAutoDereference(
420 cmExpandedCommandArgument& newArg, std::string& errorString,
421 MessageType& status, bool const oneArg) const
422 {
423 // Use the policy if it is set.
424 if (this->Policy12Status == cmPolicies::NEW) {
425 return this->GetBooleanValue(newArg);
426 }
427 if (this->Policy12Status == cmPolicies::OLD) {
428 return this->GetBooleanValueOld(newArg, oneArg);
429 }
430
431 // Check policy only if old and new results differ.
432 const auto newResult = this->GetBooleanValue(newArg);
433 const auto oldResult = this->GetBooleanValueOld(newArg, oneArg);
434 if (newResult != oldResult) {
435 switch (this->Policy12Status) {
436 case cmPolicies::WARN:
437 errorString = "An argument named \"" + newArg.GetValue() +
438 "\" appears in a conditional statement. " +
439 cmPolicies::GetPolicyWarning(cmPolicies::CMP0012);
440 status = MessageType::AUTHOR_WARNING;
441 CM_FALLTHROUGH;
442 case cmPolicies::OLD:
443 return oldResult;
444 case cmPolicies::REQUIRED_IF_USED:
445 case cmPolicies::REQUIRED_ALWAYS: {
446 errorString = "An argument named \"" + newArg.GetValue() +
447 "\" appears in a conditional statement. " +
448 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0012);
449 status = MessageType::FATAL_ERROR;
450 break;
451 }
452 case cmPolicies::NEW:
453 break;
454 }
455 }
456 return newResult;
457 }
458
459 template <int N>
matchKeysImpl(const cmExpandedCommandArgument &)460 inline int cmConditionEvaluator::matchKeysImpl(
461 const cmExpandedCommandArgument&)
462 {
463 // Zero means "not found"
464 return 0;
465 }
466
467 template <int N, typename T, typename... Keys>
matchKeysImpl(const cmExpandedCommandArgument & arg,T current,Keys...key)468 inline int cmConditionEvaluator::matchKeysImpl(
469 const cmExpandedCommandArgument& arg, T current, Keys... key)
470 {
471 if (this->IsKeyword(current, arg)) {
472 // Stop searching as soon as smth has found
473 return N;
474 }
475 return matchKeysImpl<N + 1>(arg, key...);
476 }
477
478 template <typename... Keys>
matchKeys(const cmExpandedCommandArgument & arg,Keys...key)479 inline int cmConditionEvaluator::matchKeys(
480 const cmExpandedCommandArgument& arg, Keys... key)
481 {
482 // Get index of the matched key (1-based)
483 return matchKeysImpl<1>(arg, key...);
484 }
485
486 //=========================================================================
487 // level 0 processes parenthetical expressions
HandleLevel0(cmArgumentList & newArgs,std::string & errorString,MessageType & status)488 bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs,
489 std::string& errorString,
490 MessageType& status)
491 {
492 for (auto arg = newArgs.begin(); arg != newArgs.end(); ++arg) {
493 if (this->IsKeyword(keyParenL, *arg)) {
494 // search for the closing paren for this opening one
495 auto depth = 1;
496 auto argClose = std::next(arg);
497 for (; argClose != newArgs.end() && depth; ++argClose) {
498 depth += int(this->IsKeyword(keyParenL, *argClose)) -
499 int(this->IsKeyword(keyParenR, *argClose));
500 }
501 if (depth) {
502 errorString = "mismatched parenthesis in condition";
503 status = MessageType::FATAL_ERROR;
504 return false;
505 }
506
507 // store the reduced args in this vector
508 auto argOpen = std::next(arg);
509 const std::vector<cmExpandedCommandArgument> subExpr(
510 argOpen, std::prev(argClose));
511
512 // now recursively invoke IsTrue to handle the values inside the
513 // parenthetical expression
514 const auto value = this->IsTrue(subExpr, errorString, status);
515 *arg = cmExpandedCommandArgument(bool2string(value), true);
516 argOpen = std::next(arg);
517 // remove the now evaluated parenthetical expression
518 newArgs.erase(argOpen, argClose);
519 }
520 }
521 return true;
522 }
523
524 //=========================================================================
525 // level one handles most predicates except for NOT
HandleLevel1(cmArgumentList & newArgs,std::string &,MessageType &)526 bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&,
527 MessageType&)
528 {
529 const auto policy64IsOld = this->Policy64Status == cmPolicies::OLD ||
530 this->Policy64Status == cmPolicies::WARN;
531
532 for (auto args = newArgs.make2ArgsIterator(); args.current != newArgs.end();
533 args.advance(newArgs)) {
534
535 auto policyCheck = [&, this](const cmPolicies::PolicyID id,
536 const cmPolicies::PolicyStatus status,
537 const cm::static_string_view kw) {
538 if (status == cmPolicies::WARN && this->IsKeyword(kw, *args.current)) {
539 std::ostringstream e;
540 e << cmPolicies::GetPolicyWarning(id) << "\n"
541 << kw
542 << " will be interpreted as an operator "
543 "when the policy is set to NEW. "
544 "Since the policy is not set the OLD behavior will be used.";
545
546 this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
547 }
548 };
549
550 // NOTE Checking policies for warnings are not require an access to the
551 // next arg. Check them first!
552 policyCheck(cmPolicies::CMP0064, this->Policy64Status, keyTEST);
553
554 // NOTE Fail fast: All the predicates below require the next arg to be
555 // valid
556 if (args.next == newArgs.end()) {
557 continue;
558 }
559
560 // does a file exist
561 if (this->IsKeyword(keyEXISTS, *args.current)) {
562 newArgs.ReduceOneArg(cmSystemTools::FileExists(args.next->GetValue()),
563 args);
564 }
565 // does a directory with this name exist
566 else if (this->IsKeyword(keyIS_DIRECTORY, *args.current)) {
567 newArgs.ReduceOneArg(
568 cmSystemTools::FileIsDirectory(args.next->GetValue()), args);
569 }
570 // does a symlink with this name exist
571 else if (this->IsKeyword(keyIS_SYMLINK, *args.current)) {
572 newArgs.ReduceOneArg(cmSystemTools::FileIsSymlink(args.next->GetValue()),
573 args);
574 }
575 // is the given path an absolute path ?
576 else if (this->IsKeyword(keyIS_ABSOLUTE, *args.current)) {
577 newArgs.ReduceOneArg(
578 cmSystemTools::FileIsFullPath(args.next->GetValue()), args);
579 }
580 // does a command exist
581 else if (this->IsKeyword(keyCOMMAND, *args.current)) {
582 newArgs.ReduceOneArg(
583 bool(this->Makefile.GetState()->GetCommand(args.next->GetValue())),
584 args);
585 }
586 // does a policy exist
587 else if (this->IsKeyword(keyPOLICY, *args.current)) {
588 cmPolicies::PolicyID pid;
589 newArgs.ReduceOneArg(
590 cmPolicies::GetPolicyID(args.next->GetValue().c_str(), pid), args);
591 }
592 // does a target exist
593 else if (this->IsKeyword(keyTARGET, *args.current)) {
594 newArgs.ReduceOneArg(
595 bool(this->Makefile.FindTargetToUse(args.next->GetValue())), args);
596 }
597 // is a variable defined
598 else if (this->IsKeyword(keyDEFINED, *args.current)) {
599 const auto& var = args.next->GetValue();
600 const auto varNameLen = var.size();
601
602 auto result = false;
603 if (looksLikeSpecialVariable(var, "ENV"_s, varNameLen)) {
604 const auto env = args.next->GetValue().substr(4, varNameLen - 5);
605 result = cmSystemTools::HasEnv(env);
606 }
607
608 else if (looksLikeSpecialVariable(var, "CACHE"_s, varNameLen)) {
609 const auto cache = args.next->GetValue().substr(6, varNameLen - 7);
610 result = bool(this->Makefile.GetState()->GetCacheEntryValue(cache));
611 }
612
613 else {
614 result = this->Makefile.IsDefinitionSet(args.next->GetValue());
615 }
616 newArgs.ReduceOneArg(result, args);
617 }
618 // does a test exist
619 else if (this->IsKeyword(keyTEST, *args.current)) {
620 if (policy64IsOld) {
621 continue;
622 }
623 newArgs.ReduceOneArg(bool(this->Makefile.GetTest(args.next->GetValue())),
624 args);
625 }
626 }
627 return true;
628 }
629
630 //=========================================================================
631 // level two handles most binary operations except for AND OR
HandleLevel2(cmArgumentList & newArgs,std::string & errorString,MessageType & status)632 bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs,
633 std::string& errorString,
634 MessageType& status)
635 {
636 for (auto args = newArgs.make3ArgsIterator(); args.current != newArgs.end();
637 args.advance(newArgs)) {
638
639 int matchNo;
640
641 // NOTE Handle special case `if(... BLAH_BLAH MATCHES)`
642 // (i.e., w/o regex to match which is possibly result of
643 // variable expansion to an empty string)
644 if (args.next != newArgs.end() &&
645 this->IsKeyword(keyMATCHES, *args.current)) {
646 newArgs.ReduceOneArg(false, args);
647 }
648
649 // NOTE Fail fast: All the binary ops below require 2 arguments.
650 else if (args.next == newArgs.end() || args.nextnext == newArgs.end()) {
651 continue;
652 }
653
654 else if (this->IsKeyword(keyMATCHES, *args.next)) {
655 cmValue def = this->GetDefinitionIfUnquoted(*args.current);
656
657 std::string def_buf;
658 if (!def) {
659 def = cmValue(args.current->GetValue());
660 } else if (cmHasLiteralPrefix(args.current->GetValue(),
661 "CMAKE_MATCH_")) {
662 // The string to match is owned by our match result variables.
663 // Move it to our own buffer before clearing them.
664 def_buf = *def;
665 def = cmValue(def_buf);
666 }
667
668 this->Makefile.ClearMatches();
669
670 const auto& rex = args.nextnext->GetValue();
671 cmsys::RegularExpression regEntry;
672 if (!regEntry.compile(rex)) {
673 std::ostringstream error;
674 error << "Regular expression \"" << rex << "\" cannot compile";
675 errorString = error.str();
676 status = MessageType::FATAL_ERROR;
677 return false;
678 }
679
680 const auto match = regEntry.find(*def);
681 if (match) {
682 this->Makefile.StoreMatches(regEntry);
683 }
684 newArgs.ReduceTwoArgs(match, args);
685 }
686
687 else if ((matchNo =
688 this->matchKeys(*args.next, keyLESS, keyLESS_EQUAL, keyGREATER,
689 keyGREATER_EQUAL, keyEQUAL))) {
690
691 cmValue ldef = this->GetVariableOrString(*args.current);
692 cmValue rdef = this->GetVariableOrString(*args.nextnext);
693
694 double lhs;
695 double rhs;
696 auto parseDoubles = [&]() {
697 return std::sscanf(ldef->c_str(), "%lg", &lhs) == 1 &&
698 std::sscanf(rdef->c_str(), "%lg", &rhs) == 1;
699 };
700 // clang-format off
701 const auto result = parseDoubles() &&
702 cmRt2CtSelector<
703 std::less, std::less_equal,
704 std::greater, std::greater_equal,
705 std::equal_to
706 >::eval(matchNo, lhs, rhs);
707 // clang-format on
708 newArgs.ReduceTwoArgs(result, args);
709 }
710
711 else if ((matchNo = this->matchKeys(*args.next, keySTRLESS,
712 keySTRLESS_EQUAL, keySTRGREATER,
713 keySTRGREATER_EQUAL, keySTREQUAL))) {
714
715 const cmValue lhs = this->GetVariableOrString(*args.current);
716 const cmValue rhs = this->GetVariableOrString(*args.nextnext);
717 const auto val = (*lhs).compare(*rhs);
718 // clang-format off
719 const auto result = cmRt2CtSelector<
720 std::less, std::less_equal,
721 std::greater, std::greater_equal,
722 std::equal_to
723 >::eval(matchNo, val, 0);
724 // clang-format on
725 newArgs.ReduceTwoArgs(result, args);
726 }
727
728 else if ((matchNo =
729 this->matchKeys(*args.next, keyVERSION_LESS,
730 keyVERSION_LESS_EQUAL, keyVERSION_GREATER,
731 keyVERSION_GREATER_EQUAL, keyVERSION_EQUAL))) {
732 const auto op = MATCH2CMPOP[matchNo - 1];
733 const std::string& lhs = this->GetVariableOrString(*args.current);
734 const std::string& rhs = this->GetVariableOrString(*args.nextnext);
735 const auto result = cmSystemTools::VersionCompare(op, lhs, rhs);
736 newArgs.ReduceTwoArgs(result, args);
737 }
738
739 // is file A newer than file B
740 else if (this->IsKeyword(keyIS_NEWER_THAN, *args.next)) {
741 auto fileIsNewer = 0;
742 cmsys::Status ftcStatus = cmSystemTools::FileTimeCompare(
743 args.current->GetValue(), args.nextnext->GetValue(), &fileIsNewer);
744 newArgs.ReduceTwoArgs(
745 (!ftcStatus || fileIsNewer == 1 || fileIsNewer == 0), args);
746 }
747
748 else if (this->IsKeyword(keyIN_LIST, *args.next)) {
749
750 if (this->Policy57Status != cmPolicies::OLD &&
751 this->Policy57Status != cmPolicies::WARN) {
752
753 cmValue lhs = this->GetVariableOrString(*args.current);
754 cmValue rhs = this->Makefile.GetDefinition(args.nextnext->GetValue());
755
756 newArgs.ReduceTwoArgs(
757 rhs && cm::contains(cmExpandedList(*rhs, true), *lhs), args);
758 }
759
760 else if (this->Policy57Status == cmPolicies::WARN) {
761 std::ostringstream e;
762 e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0057)
763 << "\n"
764 "IN_LIST will be interpreted as an operator "
765 "when the policy is set to NEW. "
766 "Since the policy is not set the OLD behavior will be used.";
767
768 this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
769 }
770 }
771 }
772 return true;
773 }
774
775 //=========================================================================
776 // level 3 handles NOT
HandleLevel3(cmArgumentList & newArgs,std::string & errorString,MessageType & status)777 bool cmConditionEvaluator::HandleLevel3(cmArgumentList& newArgs,
778 std::string& errorString,
779 MessageType& status)
780 {
781 for (auto args = newArgs.make2ArgsIterator(); args.next != newArgs.end();
782 args.advance(newArgs)) {
783 if (this->IsKeyword(keyNOT, *args.current)) {
784 const auto rhs = this->GetBooleanValueWithAutoDereference(
785 *args.next, errorString, status);
786 newArgs.ReduceOneArg(!rhs, args);
787 }
788 }
789 return true;
790 }
791
792 //=========================================================================
793 // level 4 handles AND OR
HandleLevel4(cmArgumentList & newArgs,std::string & errorString,MessageType & status)794 bool cmConditionEvaluator::HandleLevel4(cmArgumentList& newArgs,
795 std::string& errorString,
796 MessageType& status)
797 {
798 for (auto args = newArgs.make3ArgsIterator(); args.nextnext != newArgs.end();
799 args.advance(newArgs)) {
800
801 int matchNo;
802
803 if ((matchNo = this->matchKeys(*args.next, keyAND, keyOR))) {
804 const auto lhs = this->GetBooleanValueWithAutoDereference(
805 *args.current, errorString, status);
806 const auto rhs = this->GetBooleanValueWithAutoDereference(
807 *args.nextnext, errorString, status);
808 // clang-format off
809 const auto result =
810 cmRt2CtSelector<
811 std::logical_and, std::logical_or
812 >::eval(matchNo, lhs, rhs);
813 // clang-format on
814 newArgs.ReduceTwoArgs(result, args);
815 }
816 }
817 return true;
818 }
819