Coverage Report

Created: 2026-07-14 07:09

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