Coverage Report

Created: 2026-03-12 06:35

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