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